--- /dev/null
+<?php
+
+// Lazy ClassLoader
+function __autoload( $class_name ){
+ $s = '';
+ $a = explode( '_', $class_name );
+ foreach( $a as &$folder ){
+ if ( $s!='' )
+ $s .= '/';
+ $s .= strtolower( $folder );
+ }
+ require_once($s.'.php');
+}
+
+final class IPF{
+
+ private static $settings = array();
+
+ private static function applySettings($settings){
+ foreach($settings as $key=>$val){
+ IPF::$settings[strtolower($key)] = $val;
+ }
+ }
+
+ private static function loadSettings(){
+ $settings_file = IPF::$settings['project_path'].DIRECTORY_SEPARATOR.'settings.php';
+ IPF::$settings['settings_file'] = $settings_file;
+
+ if (file_exists($settings_file))
+ IPF::applySettings(require $settings_file);
+ else
+ throw new IPF_Exception_Settings('Configuration file does not exist: '.$settings_file);
+
+ $settings_local_file = IPF::$settings['project_path'].DIRECTORY_SEPARATOR.'settings_local.php';
+ if (file_exists($settings_local_file))
+ IPF::applySettings(require $settings_local_file);
+
+ if (!isset(IPF::$settings['dsn']))
+ throw new IPF_Exception_Settings('Please specify DSN in settings file');
+ else
+ if (!is_string(IPF::$settings['dsn']))
+ throw new IPF_Exception_Settings('DSN must be string');
+
+ if (!isset(IPF::$settings['tmp']))
+ IPF::$settings['tmp'] = '/tmp';
+ else
+ if (!is_string(IPF::$settings['tmp']))
+ throw new IPF_Exception_Settings('TMP must be string');
+
+ if (!isset(IPF::$settings['applications']))
+ throw new IPF_Exception_Settings('Please specify application list');
+ if (!is_array(IPF::$settings['applications']))
+ throw new IPF_Exception_Settings('applications must be array of string');
+
+ if (!isset(IPF::$settings['template_dirs'])){
+ IPF::$settings['template_dirs'] = array();
+ IPF::$settings['template_dirs'][] = IPF::$settings['project_path'].DIRECTORY_SEPARATOR.'templates';
+ if (array_search('IPF_Admin',IPF::$settings['applications']))
+ IPF::$settings['template_dirs'][] = IPF::$settings['ipf_path'].DIRECTORY_SEPARATOR.'ipf'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'templates';
+ }
+
+ if (!isset(IPF::$settings['debug'])){
+ IPF::$settings['debug'] = true;
+ }
+
+ if (!isset(IPF::$settings['app_base'])){
+ IPF::$settings['app_base'] = '/index.php';
+ }
+
+ if (!isset(IPF::$settings['media_url'])){
+ IPF::$settings['media_url'] = '/media/';
+ }
+
+ if (!isset(IPF::$settings['admin_media_url'])){
+ IPF::$settings['admin_media_url'] = '/ipf/ipf/admin/media/';
+ }
+
+ if (!isset(IPF::$settings['urls'])){
+ throw new IPF_Exception_Settings('Specify site url routes');
+ }
+
+ if (!isset(IPF::$settings['session_cookie_id'])){
+ IPF::$settings['session_cookie_id'] = 'sessionid';
+ }
+
+ //print_r(IPF::$settings);
+ }
+
+ public static function boot($ipf_path, $project_path)
+ {
+ IPF::$settings['ipf_path']=$ipf_path;
+ IPF::$settings['project_path']=$project_path;
+ try{
+ IPF::loadSettings();
+ }catch(IPF_Exception_Settings $e){
+ die('Setting Error: '.$e->getMessage()."\n");
+ }
+ }
+
+ private function __construct(){}
+ private function __clone(){}
+
+ public static function get($name,$default=null){
+ if (isset(IPF::$settings[$name]))
+ return IPF::$settings[$name];
+ return $default;
+ }
+
+ public static function loadFunction($function)
+ {
+ if (function_exists($function)) {
+ return;
+ }
+ $elts = explode('_', $function);
+ array_pop($elts);
+ $file = strtolower(implode(DIRECTORY_SEPARATOR, $elts)).'.php';
+ @include $file;
+
+ if (!function_exists($function))
+ throw new IPF_Exception('Impossible to load the function: '.$function.' in '.$file);
+ }
+
+ public static function factory($model, $params=null)
+ {
+ if ($params !== null)
+ return new $model($params);
+ return new $model();
+ }
+}
+
+function __($str)
+{
+ $t = trim($str);
+ return $t;
+}
--- /dev/null
+<?php
+
+class IPF_Admin_App extends IPF_Application{
+ public function __construct(){
+ parent::__construct(array(
+ 'models'=>array('AdminLog')
+ ));
+ }
+ public static function urls(){
+ return array(
+ array('regex'=>'$#', 'func'=>'IPF_Admin_Views_Index'),
+ array('regex'=>'([\w\_]+)/([\w\_]+)/$#i', 'func'=>'IPF_Admin_Views_ListItems'),
+ array('regex'=>'([\w\_]+)/([\w\_]+)/add/$#i', 'func'=>'IPF_Admin_Views_AddItem'),
+ array('regex'=>'([\w\_]+)/([\w\_]+)/([\w\_]+)/$#i', 'func'=>'IPF_Admin_Views_EditItem'),
+ array('regex'=>'([\w\_]+)/([\w\_]+)/([\w\_]+)/delete/$#i', 'func'=>'IPF_Admin_Views_DeleteItem'),
+ array('regex'=>'auth/user/([\w\_]+)/password/$#i', 'func'=>'IPF_Admin_Views_ChangePassword'),
+ array('regex'=>'login/$#i', 'func'=>'IPF_Admin_Views_Login'),
+ array('regex'=>'logout/$#i', 'func'=>'IPF_Admin_Views_Logout'),
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+#changelist { position:relative; width:100%;}
+#changelist table { width:100%; }
+.change-list .filtered table { border-right:1px solid #ddd; }
+.change-list .filtered { min-height:400px; _height:400px; }
+.change-list .filtered { background:white url(../img/changelist-bg.gif) top right repeat-y !important; }
+.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; }
+.change-list .filtered table tbody th { padding-right:10px; }
+#changelist .toplinks { border-bottom:1px solid #ccc !important; }
+#changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/nav-bg.gif) 0 180% repeat-x; overflow:hidden; }
+.change-list .filtered .paginator { border-right:1px solid #ddd; }
+
+/* CHANGELIST TABLES */
+#changelist table {border-collapse:collapse; border:1px solid #ABB0BE;}
+#changelist table thead th { white-space:nowrap; font-weight:bold; color:#666; padding:2px 5px; font-size:11px; background:url(../img/hbg.gif) top left repeat-x; border-left:1px solid #ABB0BE; border-bottom:1px solid #ABB0BE; }
+#changelist table tbody td { border-left: 1px solid #ddd; }
+#changelist table tfoot { color: #666; }
+
+/* TOOLBAR */
+#changelist #toolbar { padding:3px; border-bottom:1px solid #ddd; background:#e1e1e1 url(../img/nav-bg.gif) top left repeat-x; color:#666; }
+#changelist #toolbar form input { font-size:11px; padding:1px 2px; }
+#changelist #toolbar form #searchbar { padding:2px; }
+#changelist #changelist-search img { vertical-align:middle; }
+
+/* FILTER COLUMN */
+#changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; }
+#changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; }
+#changelist-filter h3 { font-size:12px; margin-bottom:0; }
+#changelist-filter ul { padding-left:0;margin-left:10px;_margin-right:-10px; }
+#changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; }
+#changelist-filter a { color:#999; }
+#changelist-filter a:hover { color:#036; }
+#changelist-filter li.selected { border-left:5px solid #ccc; padding-left:5px;margin-left:-10px; }
+#changelist-filter li.selected a { color:#5b80b2 !important; }
+
+/* DATE DRILLDOWN */
+.change-list ul.toplinks { display:block; background:white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left; padding:0 !important; margin:0 !important; width:100%; }
+.change-list ul.toplinks li { float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none; }
+.change-list ul.toplinks .date-back a { color:#999; }
+.change-list ul.toplinks .date-back a:hover { color:#036; }
--- /dev/null
+body.dashboard { margin:0; padding:0; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:url('../img/topbg.gif') left top repeat-x; }
+body.simple { margin:10; padding:10; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; }
+
+a:link, a:visited { color: #005681; text-decoration:none; }
+a:hover { color: #444444; text-decoration:underline; }
+a img { border:none; }
+
+p, ol, ul, dl { margin:.2em 0 .8em 0; font-size:12px; }
+p { padding:0; line-height:140%; }
+h1,h2,h3,h4,h5 { font-weight:bold; }
+h1 { font-weight:normal; font-size:18px; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; }
+h2 { font-size:16px; margin:1em 0 .5em 0; }
+h2.subhead { font-weight:normal;margin-top:0; }
+h3 { font-size:14px; margin:.8em 0 .3em 0; color:#666; font-weight:bold; }
+h4 { font-size:12px; margin:1em 0 .8em 0; padding-bottom:3px; }
+h5 { font-size:10px; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; }
+ul li { list-style-type:square; padding:1px 0; }
+ul.plainlist { margin-left:0 !important; }
+ul.plainlist li { list-style-type:none; }
+li ul { margin-bottom:0; }
+li, dt, dd { font-size:11px; line-height:14px; }
+dt { font-weight:bold; margin-top:4px; }
+dd { margin-left:0; }
+form { margin:0; padding:0; }
+fieldset { margin:0; padding:0; }
+blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; }
+code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; }
+pre.literal-block { margin:10px; background:#eee; padding:6px 8px; }
+code strong { color:#930; }
+hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; }
+
+
+
+/* PAGE STRUCTURE */
+#ipfcontainer { position:relative; width:100%; min-width:760px; }
+#ipfcontent { margin:10px 10px; }
+#ipfheader { width:100%;}
+#ipfcontent-main { float:left; width:100%; }
+#ipfcontent-related { float:right; width:220px; position:relative; margin-right:-230px; }
+#ipffooter { clear:both; padding:10px; }
+#ipfver {margin-left:10px; font-size:.8em; color:#999;}
+
+/* COLUMN TYPES */
+.colMS { margin-right:245px !important; }
+.colSM { margin-left:245px !important; }
+.colSM #ipfcontent-related { float:left; margin-right:0; margin-left:-230px; }
+.colSM #ipfcontent-main { float:right; }
+.popup .colM { width:95%; }
+.subcol { float:left; width:46%; margin-right:15px; }
+.dashboard #content { width:500px; }
+
+/* HEADER */
+#ipfheader { overflow:hidden; height:43px; }
+/* #417690 #ffc #33436a*/
+#ipfheader a:hover { text-decoration:underline; }
+#ipfbranding h1 { padding:10px 10px 0 10px; font-size:18px; margin:0; font-weight:normal; color:#444444; }
+#ipfuser-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; }
+
+/* SIDEBAR */
+#ipfcontent-related h3 { font-size:12px; color:#666; margin-bottom:3px; }
+#ipfcontent-related h4 { font-size:11px; }
+
+/* TABLES */
+table { border-collapse:collapse; border-color:#ABB0BE; }
+td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; }
+th { text-align:left; font-size:12px; }
+thead th { font-weight:bold; color:#666; padding:2px 5px; font-size:11px; background:url(../img/hbg.gif) top left repeat-x; border-left:1px solid #ABB0BE; border-bottom:1px solid #ABB0BE; }
+thead th:first-child { border-left:none !important; }
+.superwide table th, .superwide table td, .superwide table input, .superwide table select { font-size:10px; }
+.module table { border-collapse: collapse; }
+thead th.optional { font-weight:normal !important; }
+#ipfhome-page table.module tr:hover { background:#EDF3FE; }
+fieldset { border:1px solid #eee; }
+fieldset table { width:100%; }
+fieldset table th{ width:100px; }
+tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; }
+tr.alt { background:#f6f6f6; }
+.row1 { background:#EDF3FE; }
+.row2 { background:white; }
+table#ipfchange-history { width:100%; }
+table#ipfchange-history tbody th { width:16em; }
+
+/* TABLE SORTING */
+thead th a:link, thead th a:visited { color:#666; display:block; }
+table thead th.sorted { background-position:bottom left !important; }
+table thead th.sorted a { padding-right:13px; }
+table thead th.ascending a { background:url(../img/arrow-down.gif) right .4em no-repeat; }
+table thead th.descending a { background:url(../img/arrow-up.gif) right .4em no-repeat; }
+
+/* MODULES */
+.module { border:1px solid #ABB0BE; margin-bottom:5px; }
+.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; }
+.module blockquote { margin-left:12px; }
+.module ul, .module ol { margin-left:1.5em; }
+.module h2, .module caption { border-bottom:1px solid #ABB0BE; margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; background:url(../img/hbg.gif) left top repeat-x; color:#444444; font-weight:bold; }
+.module caption { border:1px solid #ABB0BE; }
+.module h3 { margin-top:.6em; }
+#ipfcontent-related .module h2 { background:#eee url(../img/nav-bg.gif) bottom left repeat-x; color:#666; }
+#ipfcontent-main .verbose .actionlist { float:right; font-size:10px; width:17em; position:relative; top:-1.6em; margin:0 8px; }
+
+/* DASHBOARD */
+.dashboard .module table th { width:100%; }
+.dashboard .module table td { white-space:nowrap; }
+.dashboard .module table td a { display:block; padding-right:.6em; }
+
+/* RECENT ACTIONS MODULE */
+.module ul.actionlist { margin-left:0; }
+ul.actionlist li { list-style-type:none; }
+
+/* FORM DEFAULTS */
+input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; border:1px solid #ABB0BE; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; }
+textarea { vertical-align:top !important; }
+input[type=checkbox], input[type=radio] { border:none; }
+
+/* FORM BUTTONS */
+input[type=submit], input[type=button], .submit-row input { background:white url(../img/nav-bg.gif) bottom repeat-x; padding:2px; color:black; }
+input[type=submit]:active, input[type=button]:active { background-image:url(../img/nav-bg.gif); background-position:bottom; }
+input[type=submit].default, .submit-row input.default { border:2px solid #888; background: url(../img/hbg.gif) center repeat-x; font-weight:bold; color:#888; }
+input[type=submit].default:active { background-image:url(../img/default-bg-reverse.gif); background-position:top; }
+.submit-row { padding:5px 7px; text-align:right; background:url(../img/topbg.gif) top left repeat-x; border:1px solid #ABB0BE; margin:5px 0; }
+.submit-row input { margin:0 0 0 5px; }
+.submit-row .float-left { padding-top:.1em; }
+
+/* FORM ROWS */
+.ipfform-row { clear:both; padding:8px 12px; font-size:11px; }
+html>body .ipfform-row { border-bottom:1px solid #eee; }
+.ipfform-row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ipfform-row img, .ipfform-row input { vertical-align:middle; }
+form .form-row p { padding-left:0; font-size:11px; }
+
+/* FORM LABELS */
+form h4 { margin:0 !important; padding:0 !important; border:none !important; }
+label { font-weight:normal !important; color:#666; font-size:12px; }
+label.inline { margin-left:20px; }
+.required label, label.required { font-weight:bold !important; color:#333 !important; }
+
+/* RADIO BUTTONS */
+form ul.radiolist li { list-style-type:none; }
+form ul.radiolist label { float:none; display:inline; }
+form ul.inline { margin-left:0; padding:0; }
+form ul.inline li { float:left; padding-right:7px; }
+
+/* ALIGNED FIELDSETS */
+.aligned label { display:block; padding:0 1em 3px 0; float:left; text-align:left; width:8em; }
+.aligned label.inline { display:inline; float:none; }
+.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { width:350px; }
+form .aligned p, form .aligned ul { margin-left:7em; padding-left:30px; }
+form .aligned table p { margin-left:0; padding-left:0; }
+form .aligned p.help { padding-left:38px; }
+.aligned .vCheckboxLabel { float:none !important; display:inline; }
+.colM .aligned .vLargeTextField, colM .aligned .vXMLLargeTextField { width:610px; }
+.checkbox-row p.help { margin-left:0; padding-left:0 !important; }
+
+/* WIDE FIELDSETS */
+.wide label { width:15em !important; }
+form .wide p { margin-left:15em; }
+form .wide p.help { padding-left:38px; }
+.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { width:450px; }
+
+/* COLLAPSED FIELDSETS */
+fieldset.collapsed * { display:none; }
+fieldset.collapsed h2, fieldset.collapsed { display:block !important; }
+fieldset.collapsed .collapse-toggle { display: inline !important; }
+fieldset.collapse h2 a.collapse-toggle { color:#ffc; }
+fieldset.collapse h2 a.collapse-toggle:hover { text-decoration:underline; }
+.hidden { display:none; }
+
+/* MONOSPACE TEXTAREAS */
+fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
+
+/* MESSAGES & ERRORS */
+ul.messagelist { padding:0 0 5px 0; margin:0; }
+ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/icon_success.gif) 5px .3em no-repeat; }
+.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/icon_error.gif) 5px .3em no-repeat; }
+ul.errorlist { margin:0 !important; padding:0 !important; }
+.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid maroon; color:maroon; background:#FEE url(../img/icon_alert.gif) 3px 2px no-repeat; }
+td ul.errorlist { margin:0 !important; padding:0 !important; }
+td ul.errorlist li { margin:0 !important; }
+.error { background:#ffc; }
+.error input, .error select { border:1px solid red; }
+div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; }
+div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/icon_error.gif) 5px .3em no-repeat; }
+
+/* ACTION ICONS */
+.addlink { padding-left:12px; background:url(../img/icon_addlink.gif) 0 .2em no-repeat; }
+.changelink { padding-left:12px; background:url(../img/icon_changelink.gif) 0 .2em no-repeat; }
+.deletelink { padding-left:12px; background:url(../img/icon_deletelink.gif) 0 .2em no-repeat; }
+a.deletelink:link, a.deletelink:visited { color:#CC3434; }
+a.deletelink:hover { color:#993333; }
+
+/* OBJECT TOOLS */
+.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; margin-bottom:5px; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; }
+.form-row .object-tools { margin-top:0; margin-bottom:0; }
+.object-tools li { display:block; float:left; background:url(../img/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; }
+.object-tools li:hover { background:url(../img/tool-left_over.gif) 0 0 no-repeat; }
+.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/tool-right.gif) 100% 0 no-repeat; }
+.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/tool-right_over.gif) 100% 0 no-repeat; }
+.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; }
+.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/tooltag-arrowright_over.gif) top right no-repeat; }
+.object-tools a.addlink { background:#999 url(../img/tooltag-add.gif) top right no-repeat; padding-right:28px; }
+.object-tools a.addlink:hover { background:#5b80b2 url(../img/tooltag-add_over.gif) top right no-repeat; }
+
+/* INLINE CONTROLS */
+#ipfinline-controls { font-weight:bold; font-size:12px; }
+#ipfinline-specific-controls { margin-left:6px; padding:0 8px; border-left:6px solid #ABB0BE; }
+
+/* BREADCRUMBS */
+div.breadcrumbs { padding:2px 8px 3px 10px; font-size:11px; color:#444; text-align:left; }
+div.breadcrumbs a{ text-decoration:none;}
+div.breadcrumbs a:hover;{ text-decoration:underline; color:#444; }
+
+/* SELECTOR (FILTER INTERFACE) */
+.selector { width:580px; float:left; }
+.selector select { width:270px; height:170px; }
+.selector-available, .selector-chosen { float:left; width:270px; text-align:center; margin-bottom:5px; }
+.selector-available h2, .selector-chosen h2 { border:1px solid #ABB0BE; }
+.selector .selector-available h2 { background:white url(../img/nav-bg.gif) bottom left repeat-x; color:#666; }
+.selector .selector-filter { background:white; border:1px solid #ABB0BE; border-width:0 1px; padding:3px; color:#999; font-size:10px; margin:0; text-align:left; }
+.selector .selector-chosen .selector-filter { padding:4px 5px; }
+.selector .selector-available input { width:230px; }
+.selector ul.selector-chooser { float:left; width:22px; height:50px; background:url(../img/chooser-bg.gif) top center no-repeat; margin:13% 3px 0 3px; padding:0; }
+.selector-chooser li { margin:0; padding:3px; list-style-type:none; }
+.selector select { margin-bottom:5px; margin-top:0; }
+.selector-add, .selector-remove { width:16px; height:16px; display:block; text-indent:-3000px; }
+.selector-add { background:url(../img/selector-add.gif) top center no-repeat; margin-bottom:2px; }
+.selector-remove { background:url(../img/selector-remove.gif) top center no-repeat; }
+a.selector-chooseall, a.selector-clearall { display:block; width:6em; text-align:left; margin-left:auto; margin-right:auto; font-weight:bold; color:#666; padding:3px 0 3px 18px; }
+a.selector-chooseall:hover, a.selector-clearall:hover { color:#036; }
+a.selector-chooseall { width:7em; background:url(../img/selector-addall.gif) left center no-repeat; }
+a.selector-clearall { background:url(../img/selector-removeall.gif) left center no-repeat; }
+
+/* Stacked selectors for long items */
+.stacked { float:left; width:500px; }
+.stacked select { width:480px; height:100px; }
+.stacked .selector-available, .stacked .selector-chosen { width:480px; }
+.stacked .selector-available { margin-bottom:0; }
+.stacked .selector-available input { width:442px; }
+.stacked ul.selector-chooser { height:22px; width:50px; margin:0 0 3px 40%; background:url(../img/chooser_stacked-bg.gif) top center no-repeat; }
+.stacked .selector-chooser li { float:left; padding:3px 3px 3px 5px; }
+.stacked .selector-chooseall, .stacked .selector-clearall { display:none; }
+.stacked .selector-add { background-image:url(../img/selector_stacked-add.gif); }
+.stacked .selector-remove { background-image:url(../img/selector_stacked-remove.gif); }
+
+/* DATE AND TIME */
+p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.datetime span { font-size:11px; font-weight:normal; color:#ABB0BE; white-space:nowrap; }
+.vDateField { margin-left:4px; }
+table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
+
+/* FILE UPLOADS */
+p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.file-upload a { font-weight:normal; }
+.file-upload .deletelink { margin-left:5px; }
+
+/* CALENDARS & CLOCKS */
+.calendarbox, .clockbox { margin:5px auto; font-size:11px; width: 16em; text-align: center; background:white; position:relative; }
+.clockbox { width:9em; }
+.calendar { margin:0; padding: 0; }
+.calendar table { margin: 0; padding: 0; border-collapse:collapse; background:white; width:99%; }
+.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
+.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
+.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
+.calendar td.selected a { background: #C9DBED; }
+.calendar td.nonday { background:#efefef; }
+.calendar td.today a { background:#ffc; }
+.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
+.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
+.calendar td a:active, .timelist a:active { background: #036; color:white; }
+.calendarnav { font-size:10px; text-align: center; color:#ABB0BE; margin:0; padding:1px 3px; }
+.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
+.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ABB0BE; }
+.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(../img/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
+.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
+.calendarnav-previous { top:0; left:0; }
+.calendarnav-next { top:0; right:0; }
+.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; }
+.calendar-cancel a { padding:2px; color:#999; }
+ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
+.timelist a { padding:2px; }
+
+/* ORDERING WIDGET */
+ul#orderthese { position:absolute; top:8em; right:0; width:240px; padding:0; margin:0; list-style-type:none; }
+ul#orderthese li { list-style-type:none; display:block; padding:0; margin:6px 0; width:214px; background:#f6f6f6; white-space:nowrap; overflow:hidden; }
+ul#orderthese li span { display:block; border:1px solid #e7e7e7; background:transparent url(../img/nav-bg-grabber.gif) top left repeat-y; font-size:10px !important; padding:4px 6px 4px 12px; }
+ul#orderthese span:hover { background-color:#efefef; }
+
+/* PAGINATOR */
+.paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; }
+.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ABB0BE; background:white; text-decoration:none; }
+.paginator a.showall { padding:0 !important; border:none !important; }
+.paginator a.showall:hover { color:#036 !important; background:transparent !important; }
+.paginator .end { border-width:2px !important; margin-right:6px; }
+.paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; }
+.paginator a:hover { color:white; background:#5b80b2; border-color:#036; }
+
+/* TEXT STYLES & MODIFIERS */
+.small { font-size:11px; }
+.tiny { font-size:10px; }
+p.tiny { margin-top:-2px; }
+.mini { font-size:9px; }
+p.mini { margin-top:-3px; }
+.help, p.help { font-size:10px !important; color:#999; }
+p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; }
+.quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; }
+.quiet strong { font-weight:bold !important; }
+.float-right { float:right; }
+.float-left { float:left; }
+.clear { clear:both; }
+.align-left { text-align:left; }
+.align-right { text-align:right; }
+.example { margin:10px 0; padding:5px 10px; background:#efefef; }
+.nowrap { white-space:nowrap; }
+
+/* CUSTOM FORM FIELDS */
+.vSelectMultipleField { vertical-align:top !important; }
+.vCheckboxField { border:none; }
+.vDateField, .vTimeField { margin-right:2px; }
+.vFileUploadField { border:none; }
+.vURLField { width:380px; }
+.vLargeTextField, .vXMLLargeTextField { width:480px; }
+.colM .vLargeTextField, .colM .vXMLLargeTextField { width:720px; }
+body.core-flatfile #id_content { height: 400px; }
+.module table .vPositiveSmallIntegerField { width: 22px; }
\ No newline at end of file
--- /dev/null
+* html #container { position:static; } /* keep header from flowing off the page */
+* html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */
+* html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */
+* html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */
+* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */
+* html #content { width /**/: 768px; } /* fixed width for IE5 */
+* html #content-main { width /**/: 535px; } /* fixed width for IE5 */
\ No newline at end of file
--- /dev/null
+@import url(global.css);
+@import url(changelists.css);
+/*\*/ @import "patch-iewin.css"; /**/
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Admin_Model{
+ static $models = array();
+ public static function register($classModel, $classAdmin){
+ IPF_Admin_Model::$models[$classModel] = new $classAdmin($classModel);
+ }
+
+ public static function isModelRegister($classModel){
+ if (array_key_exists($classModel, IPF_Admin_Model::$models))
+ return true;
+ return false;
+ }
+
+ public static function getModelAdmin($classModel){
+ if (array_key_exists($classModel, IPF_Admin_Model::$models))
+ return IPF_Admin_Model::$models[$classModel];
+ return null;
+ }
+
+ var $modelName = null;
+
+ public function __construct($modelName){
+ $this->modelName = $modelName;
+ }
+
+ protected function _setupEditForm(&$form){
+ $this->_setupForm(&$form);
+ }
+
+ protected function _setupAddForm(&$form){
+ $this->_setupForm(&$form);
+ }
+
+ protected function _setupForm(&$form){
+ }
+
+ public function fields(){
+ return null;
+ }
+
+ public function ListItemsHeader(){
+ $this->header = array();
+ if (method_exists($this,'list_display'))
+ $this->names = $this->list_display();
+ else
+ $this->names = $this->qe->getTable()->getColumnNames();
+
+ foreach ($this->names as $name){
+ $this->header[$name] = new IPF_Template_ContextVars(array(
+ 'title'=>IPF_Utils::humanTitle($name),
+ 'name'=>$name,
+ 'sortable'=>null,
+ ));
+ }
+ return $this->header;
+ }
+
+ public function ListItemsQuery(){
+ $query = IPF_ORM_Query::create();
+ $this->q = $query->select("o.*")->from($this->modelName.' o');
+ }
+
+ public function ListRow($o){
+ $row = array();
+
+ foreach($this->header as &$h){
+ $t = $o->getTable()->getTypeOf($h['name']);
+ $str = $o->$h['name'];
+ if ($t=='boolean'){
+ if ($str)
+ $str = '<img src="'.IPF::get('admin_media_url').'img/icon-yes.gif" alt="True">';
+ else
+ $str = '<img src="'.IPF::get('admin_media_url').'img/icon-no.gif" alt="True">';
+ }
+ $row[$h['name']] = $str;
+ }
+ $this->LinksRow(&$row, &$o);
+
+
+ return $row;
+ }
+
+ protected function LinksRow(&$row, &$o){
+ if (method_exists($this,'list_display_links')){
+ $links_display = $this->list_display_links();
+ }else{
+ $links_display = null;
+ $i = 1;
+ }
+ foreach($row as $name=>&$v){
+ if ($links_display){
+ if (array_search($name, $links_display)!==false)
+ $v = '<a href="'.$this->UrlForResult(&$o).'">'.$v.'</a>';
+ }else{
+ if ($i==1)
+ $v = '<a href="'.$this->UrlForResult(&$o).'">'.$v.'</a>';
+ $i++;
+ }
+ }
+ }
+
+ protected function UrlForResult(&$o){
+ return $o->__get($this->qe->getTable()->getIdentifier()).'/';
+ }
+
+ // Views Function
+ public function AddItem($request, $lapp, $lmodel){
+ $model = new $this->modelName();
+ if ($request->method == 'POST'){
+ $form = IPF_Shortcuts::GetFormForModel($model,$request->POST,array('user_fields'=>$this->fields()));
+ $this->_setupAddForm(&$form);
+ if ($form->isValid()) {
+ $item = $form->save();
+ AdminLog::logAction($request, $item, AdminLog::ADDITION);
+ $url = IPF_HTTP_URL_urlForView('IPF_Admin_Views_ListItems', array($lapp, $lmodel));
+ return new IPF_HTTP_Response_Redirect($url);
+ }
+ }
+ else{
+ $form = IPF_Shortcuts::GetFormForModel($model,null,array('user_fields'=>$this->fields()));
+ $this->_setupAddForm(&$form);
+
+ }
+ $context = array(
+ 'page_title'=>'Add '.$this->modelName,
+ 'classname'=>$this->modelName,
+ 'form'=>$form,
+ 'lapp'=>$lapp,
+ 'lmodel'=>$lmodel,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/add.html', $context, $request);
+ }
+
+ public function EditItem($request, $lapp, $lmodel, $o){
+ if ($request->method == 'POST'){
+ $form = IPF_Shortcuts::GetFormForModel($o,$request->POST,array('user_fields'=>$this->fields()));
+ $this->_setupEditForm(&$form);
+ if ($form->isValid()) {
+ $item = $form->save();
+ AdminLog::logAction($request, $item, AdminLog::CHANGE);
+ $url = IPF_HTTP_URL_urlForView('IPF_Admin_Views_ListItems', array($lapp, $lmodel));
+ return new IPF_HTTP_Response_Redirect($url);
+ }
+ }
+ else{
+ $form = IPF_Shortcuts::GetFormForModel($o,$o->getData(),array('user_fields'=>$this->fields()));
+ $this->_setupEditForm(&$form);
+ }
+ $context = array(
+ 'page_title'=>'Edit '.$this->modelName,
+ 'classname'=>$this->modelName,
+ 'object'=>$o,
+ 'form'=>$form,
+ 'lapp'=>$lapp,
+ 'lmodel'=>$lmodel,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/change.html', $context, $request);
+ }
+
+ public function DeleteItem($request, $lapp, $lmodel, $o){
+ if ($request->method == 'POST'){
+ AdminLog::logAction($request, $o, AdminLog::DELETION);
+ $o->delete();
+ $url = IPF_HTTP_URL_urlForView('IPF_Admin_Views_ListItems', array($lapp, $lmodel));
+ return new IPF_HTTP_Response_Redirect($url);
+ }
+ $context = array(
+ 'page_title'=>'Delete '.$this->modelName,
+ 'classname'=>$this->modelName,
+ 'object'=>$o,
+ 'form'=>$form,
+ 'lapp'=>$lapp,
+ 'lmodel'=>$lmodel,
+ 'affected'=>array(),
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/delete.html', $context, $request);
+ }
+
+ public function ListItems($request){
+ $this->ListItemsQuery();
+ $this->qe = $this->q->execute();
+ $this->ListItemsHeader();
+ //print_r($this->qe->getTable()->getIdentifier());
+ $context = array(
+ 'page_title'=>$this->modelName.' List',
+ 'header'=>$this->header,
+ 'classname'=>$this->modelName,
+ 'objects'=>$this->qe,
+ 'classname'=>$this->modelName,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/items.html', $context, $request);
+ }
+}
--- /dev/null
+AdminLog:
+ tableName: admin_log
+ actAs:
+ Timestampable:
+ updated:
+ disabled: true
+ columns:
+ username: string(32)
+ user_id: integer
+ object_id: integer
+ object_class: string(200)
+ object_repr: string(200)
+ action_flag: integer
+ change_message: string(200)
+
+ indexes:
+ idx_object_id:
+ fields: object_id
+ idx_object_class:
+ fields: object_class
+ idx_created_at:
+ fields: created_at
+ idx_action_flag:
+ fields: action_flag
+
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
--- /dev/null
+<?php
+
+class AdminLog extends BaseAdminLog
+{
+ const ADDITION = 1;
+ const CHANGE = 2;
+ const DELETION = 3;
+
+ public static function logAction($request, $object, $action_flag, $message=''){
+ $log = new AdminLog();
+ $log->username = $request->user->username;
+ $log->user_id = $request->user->id;
+ $log->object_id = $object->id;
+ $log->object_class = get_class($object);
+ $log->object_repr = (string)$object;
+ $log->action_flag = $action_flag;
+ $log->change_message = $message;
+ $log->save();
+ }
+
+ public function is_addition(){
+ if ($this->action_flag==AdminLog::ADDITION)
+ return true;
+ return false;
+ }
+
+ public function is_change(){
+ if ($this->action_flag==AdminLog::CHANGE)
+ return true;
+ return false;
+ }
+
+ public function is_deletion(){
+ if ($this->action_flag==AdminLog::DELETION)
+ return true;
+ return false;
+ }
+
+ public function GetAdminUrl(){
+ return IPF_HTTP_URL_urlForView('IPF_Admin_Views_Index').IPF_Utils::appLabelByModel($this->object_class).'/'.strtolower($this->object_class).'/'.$this->object_id.'/';
+ }
+
+}
+
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseAdminLog extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('admin_log');
+ $this->hasColumn('username', 'string', 32, array('type' => 'string', 'length' => '32'));
+ $this->hasColumn('user_id', 'integer', null, array('type' => 'integer'));
+ $this->hasColumn('object_id', 'integer', null, array('type' => 'integer'));
+ $this->hasColumn('object_class', 'string', 200, array('type' => 'string', 'length' => '200'));
+ $this->hasColumn('object_repr', 'string', 200, array('type' => 'string', 'length' => '200'));
+ $this->hasColumn('action_flag', 'integer', null, array('type' => 'integer'));
+ $this->hasColumn('change_message', 'string', 200, array('type' => 'string', 'length' => '200'));
+
+
+ $this->index('idx_object_id', array('fields' => 'object_id'));
+ $this->index('idx_object_class', array('fields' => 'object_class'));
+ $this->index('idx_created_at', array('fields' => 'created_at'));
+ $this->index('idx_action_flag', array('fields' => 'action_flag'));
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $timestampable0 = new IPF_ORM_Template_Timestampable(array('updated' => array('disabled' => true)));
+ $this->actAs($timestampable0);
+ }
+}
\ No newline at end of file
--- /dev/null
+{extends "admin/base.html"}
+
+{block breadcrumbs} <a href="{url 'IPF_Admin_Views_Index'}">Home</a> » <a href="{url 'IPF_Admin_Views_ListItems', array($lapp, $lmodel)}">{$classname}</a> » {$page_title}{/block}
+
+{block content}
+
+<div id="ipfcontent">
+ <h1>{$page_title}</h1>
+
+ <form method="post" action="">
+ <fieldset>
+ <table>
+ {$form.render_table}
+ </table>
+ </fieldset>
+
+ <div class="submit-row">
+ <input type="submit" value="Save" class="default" />
+ </div>
+ </form>
+
+</div>
+
+{/block}
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="stylesheet" href="{$ADMIN_MEDIA_URL}css/style.css" />
+ {block style}{/block}
+ {block head}{/block}
+ <title>{$page_title}</title>
+</head>
+
+<body class="simple">
+
+{block body}{/block}
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="stylesheet" href="{$ADMIN_MEDIA_URL}css/style.css" />
+ <title>{$page_title} - IPF Administration</title>
+</head>
+
+<body class="dashboard">
+
+<!-- Container -->
+<div id="ipfcontainer">
+ <!-- Header -->
+ <div id="ipfheader">
+ <div id="ipfbranding">
+ <h1 id="ipfsite-name">{trans 'IPF Administration'}</h1>
+ </div>
+ <div id="ipfuser-tools">Welcome, <strong>{$user}</strong>. <a href="{url 'IPF_Admin_Views_Logout'}">Log out</a><br /><span id="ipfver">Version: {$IPF_VER}</span></div>
+ <br class="clear" />
+ </div>
+ <!-- END Header -->
+
+ <div class="breadcrumbs">
+ {block breadcrumbs} <a href="{url 'IPF_Admin_Views_Index'}">Home</a>{/block}
+ </div>
+
+ {block content}{/block}
+
+ <div id="ipffooter"></div>
+</div>
+<!-- END Container -->
+{block scripts}{/block}
+</body>
+</html>
--- /dev/null
+{extends "admin/base.html"}
+
+{block breadcrumbs} <a href="{url 'IPF_Admin_Views_Index'}">Home</a> » <a href="{url 'IPF_Admin_Views_ListItems', array($lapp, $lmodel)}">{$classname}</a> » {$page_title}{/block}
+
+
+{block content}
+
+<div id="ipfcontent">
+ <h1>{$page_title}</h1>
+
+ <form method="post">
+ <fieldset>
+ <table>
+ {$form.render_table}
+ </table>
+ </fieldset>
+
+ <div class="submit-row">
+ <p class="float-left"><a href="{url 'IPF_Admin_Views_DeleteItem', array($lapp, $lmodel, $object.id)}" class="deletelink">Delete</a></p>
+ <input type="submit" value="Save" class="default" />
+ </div>
+ </form>
+
+</div>
+
+{/block}
+
--- /dev/null
+{extends "admin/base.html"}
+
+{block breadcrumbs} <a href="{url 'IPF_Admin_Views_Index'}">Home</a> » <a href="{url 'IPF_Admin_Views_ListItems', array($lapp, $lmodel)}">{$classname}</a> » <a href="{url 'IPF_Admin_Views_EditItem', array($lapp, $lmodel, $object.id)}">{$object}</a> » Change Password{/block}
+
+
+{block content}
+
+<div id="ipfcontent">
+ <h1>{$page_title}</h1>
+
+ <form method="post" action="">
+ <fieldset>
+ <table>
+ {$form.render_table}
+ </table>
+ </fieldset>
+
+ <div class="submit-row">
+ <input type="submit" value="Save" class="default" />
+ </div>
+ </form>
+
+</div>
+
+{/block}
+
--- /dev/null
+{extends "admin/base.html"}
+
+{block breadcrumbs} <a href="{url 'IPF_Admin_Views_Index'}">Home</a> » <a href="{url 'IPF_Admin_Views_ListItems', array($lapp, $lmodel)}">{$classname}</a> » <a href="{url 'IPF_Admin_Views_EditItem', array($lapp, $lmodel, $object.id)}">{$object}</a> » {$page_title}{/block}
+
+{block content}
+
+<div id="ipfcontent">
+ <h1>{$page_title}</h1>
+
+ <form method="post">
+ <p>Are you sure you want to delete <em>{$object}</em>?
+ </p>
+ {if count($affected) > 0}
+ <p>It will also delete the following linked elements:</p>
+ <ul>
+ {foreach $affected as $aff}<li>{$aff}</li>{/foreach}
+ </ul>
+ {/if}
+ <input type="submit" value="Yes, delete it!" class="default" />
+ </form>
+
+</div>
+
+{/block}
+
--- /dev/null
+{extends "admin/base.html"}
+
+{block content}
+
+<div id="ipfcontent" class="colMS">
+ <h1>{$page_title}</h1>
+ <div id="ipfcontent-main">
+ <div class="module">
+ {foreach $app_list as $app}
+ <h2>{$app.name|escxml}</h2>
+ <table>
+ {foreach $app.models as $model}
+ <tr>
+ <th><a href="{url 'IPF_Admin_Views_ListItems', array($app.path, $model.path)}">{$model.name}</a></th>
+ <td><a class="addlink" href="{url 'IPF_Admin_Views_AddItem', array($app.path, $model.path)}">{trans 'Add'}</a></td>
+ <td><a class="changelink" href="{url 'IPF_Admin_Views_ListItems', array($app.path, $model.path)}">{trans 'Change'}</a></td>
+ </tr>
+ {/foreach}
+ </table>
+ {/foreach}
+ </div>
+ </div>
+ <div id="ipfcontent-related">
+ <div class="module" id="ipfrecent-actions-module">
+ <h2>Recent Actions</h2>
+ <ul class="actionlist">
+ {foreach $admin_log as $log}
+ <li class="{if $log.is_addition()}addlink{/if}{if $log.is_change()}changelink{/if}{if $log.is_deletion()}deletelink{/if}">{if !$log.is_deletion()}<a href="{$log.getAdminUrl()}">{/if}{$log.object_repr}{if !$log.is_deletion()}</a>{/if}<br /><span class="mini quiet">{$log.object_class} at {$log.created_at|date} by {$log.username}</span></li>
+ {/foreach}
+ </ul>
+ </div>
+ </div>
+ <br class="clear" />
+</div>
+{/block}
+
--- /dev/null
+{extends "admin/base.html"}
+
+{block breadcrumbs} <a href="{url 'IPF_Admin_Views_Index'}">Home</a> » {$classname}{/block}
+
+
+{block content}
+
+<div id="ipfcontent">
+ <h1>{$page_title}</h1>
+ <ul class="object-tools"><li><a href="add/" class="addlink">Add {$classname}</a></li></ul>
+ <div id="changelist">
+ <table>
+ <thead>
+ <tr>
+ {foreach $header as $h}
+ <th>{$h.title}</th>
+ {/foreach}
+ </tr>
+ </thead>
+ <tbody>
+ {foreach $objects as $o}
+ <tr>
+ {foreach $o.ModelAdmin().ListRow($o) as $v}
+ <td>{$v|safe}</td>
+ {/foreach}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ </div>
+</div>
+
+{/block}
+
--- /dev/null
+{extends 'admin/base-simple.html'}
+
+{block body}
+
+<div style="text-align:center; margin-top:100px;">
+<div style="margin-left:auto;margin-right:auto; width:300px; text-align:left;">
+ <h1>{$page_title}</h1>
+
+ <form method="post">
+ <fieldset>
+ {if $form.message}<ul class="errorlist"><li>{$form.message}</li></ul>{/if}
+ <table>
+ {$form.render_table}
+ </table>
+ </fieldset>
+
+ <div class="submit-row">
+ <input type="submit" value="Sign In" class="default" />
+ </div>
+ </form>
+
+</div>
+</div>
+
+
+{/block}
--- /dev/null
+{extends 'admin/base-simple.html'}
+
+{block body}
+
+<div style="text-align:center; margin-top:100px;">
+<div style="margin-left:auto;margin-right:auto; width:300px; text-align:left;">
+ <h1>Logged Out</h1>
+
+ <p>Thanks for spending some quality time with the Web site today.</p>
+ <p><a href="{url 'IPF_Admin_Views_Index'}">Log in</a> again.</p>
+
+</div>
+</div>
+
+
+{/block}
--- /dev/null
+<?php
+
+function checkAdminAuth($request){
+ if ($request->user->isAnonymous())
+ return new IPF_HTTP_Response_Redirect(IPF_HTTP_URL_urlForView('IPF_Admin_Views_Login'));
+ return true;
+}
+
+function IPF_Admin_Views_Index($request, $match){
+ $ca = checkAdminAuth($request);
+ if ($ca!==true) return $ca;
+
+ $apps = array();
+ $app_list = new IPF_Template_ContextVars();
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ if (count($app->modelList())>0){
+ $models = new IPF_Template_ContextVars();
+ $models_found = false;
+ foreach($app->modelList() as $m){
+ if (IPF_Admin_Model::isModelRegister($m)){
+ $models[] = new IPF_Template_ContextVars(array('name'=>$m, 'path'=>strtolower($m)));
+ $models_found = true;
+ }
+ }
+ if ($models_found){
+ $app_list[$app->getName()] = new IPF_Template_ContextVars(array(
+ 'name' => $app->getTitle(),
+ 'path' => $app->getSlug(),
+ 'models' => $models,
+ ));
+ }
+ }
+ }
+
+ $admin_log = IPF_ORM_Query::create()->select("*")->from('AdminLog')->orderby('created_at desc')->limit(10)->execute();
+
+ $context = array(
+ 'page_title' => __('Administration'),
+ 'app_list' => $app_list,
+ 'admin_log' => $admin_log,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/index.html', $context, $request);
+}
+
+function IPF_Admin_Views_ListItems($request, $match){
+ $ca = checkAdminAuth($request);
+ if ($ca!==true) return $ca;
+
+ $lmodel = $match[2];
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ foreach($app->modelList() as $m){
+ if (strtolower($m)==$lmodel){
+ $ma = IPF_Admin_Model::getModelAdmin($m);
+ if ($ma===null)
+ return new IPF_HTTP_Response_NotFound();
+ return $ma->ListItems($request);
+ }
+ }
+ }
+}
+
+function IPF_Admin_Views_EditItem($request, $match){
+ $ca = checkAdminAuth($request);
+ if ($ca!==true) return $ca;
+
+ $lapp = $match[1];
+ $lmodel = $match[2];
+ $id = $match[3];
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ foreach($app->modelList() as $m){
+ if (strtolower($m)==$lmodel){
+ $ma = IPF_Admin_Model::getModelAdmin($m);
+ if ($ma===null)
+ return new IPF_HTTP_Response_NotFound();
+ $o = new $m();
+ $item = $o->getTable()->find($id);
+ return $ma->EditItem($request, $lapp, $lmodel, &$item);
+ }
+ }
+ }
+}
+
+function IPF_Admin_Views_DeleteItem($request, $match){
+ $ca = checkAdminAuth($request);
+ if ($ca!==true) return $ca;
+
+ $lapp = $match[1];
+ $lmodel = $match[2];
+ $id = $match[3];
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ foreach($app->modelList() as $m){
+ if (strtolower($m)==$lmodel){
+ $ma = IPF_Admin_Model::getModelAdmin($m);
+ if ($ma===null)
+ return new IPF_HTTP_Response_NotFound();
+ $o = new $m();
+ $item = $o->getTable()->find($id);
+ return $ma->DeleteItem($request, $lapp, $lmodel, &$item);
+ }
+ }
+ }
+}
+
+
+function IPF_Admin_Views_AddItem($request, $match){
+ $ca = checkAdminAuth($request);
+ if ($ca!==true) return $ca;
+
+ $lapp = $match[1];
+ $lmodel = $match[2];
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ foreach($app->modelList() as $m){
+ if (strtolower($m)==$lmodel){
+ $ma = IPF_Admin_Model::getModelAdmin($m);
+ if ($ma===null)
+ return new IPF_HTTP_Response_NotFound();
+ return $ma->AddItem($request, $lapp, $lmodel);
+ }
+ }
+ }
+}
+
+function IPF_Admin_Views_ChangePassword($request, $match){
+ $ca = checkAdminAuth($request);
+ if ($ca!==true) return $ca;
+
+ $lapp = 'auth';
+ $lmodel = 'user';
+ $id = $match[1];
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ foreach($app->modelList() as $m){
+ if (strtolower($m)==$lmodel){
+ $ma = IPF_Admin_Model::getModelAdmin($m);
+ if ($ma===null)
+ return new IPF_HTTP_Response_NotFound();
+ $o = new $m();
+ $user = $o->getTable()->find($id);
+
+ if ($request->method == 'POST'){
+ $form = new IPF_Auth_Forms_ChangePassword($request->POST);
+ if ($form->isValid()) {
+ $user->setPassword($form->cleaned_data['password1']);
+ $user->save();
+ $url = IPF_HTTP_URL_urlForView('IPF_Admin_Views_ListItems', array($lapp, $lmodel));
+ return new IPF_HTTP_Response_Redirect($url);
+ }
+ }
+ else
+ $form = new IPF_Auth_Forms_ChangePassword();
+ $context = array(
+ 'page_title'=>'Change Password: '.$user->username,
+ 'classname'=>'User',
+ 'object'=>$user,
+ 'form'=>$form,
+ 'lapp'=>$lapp,
+ 'lmodel'=>$lmodel,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/changepassword.html', $context, $request);
+ }
+ }
+ }
+}
+
+
+function IPF_Admin_Views_Login($request, $match){
+ if (!empty($request->REQUEST['next']))
+ $success_url = $request->REQUEST['next'];
+ if (trim($success_url)=='')
+ $success_url = IPF_HTTP_URL_urlForView('IPF_Admin_Views_Index');
+
+ if ($request->method == 'POST') {
+ $form = new IPF_Auth_Forms_Login($request->POST);
+ if ($form->isValid()){
+ $users = new User();
+ if (false === ($user = $users->checkCreditentials($form->cleaned_data['username'], $form->cleaned_data['password']))) {
+ $form->message = __('The login or the password is not valid. The login and the password are case sensitive.');
+ } else {
+ $request->user = $user;
+ $request->session->clear();
+ $request->session->setData('login_time', gmdate('Y-m-d H:i:s'));
+ $user->last_login = gmdate('Y-m-d H:i:s');
+ $user->save();
+ return new IPF_HTTP_Response_Redirect($success_url);
+ }
+ }
+ }
+ else
+ $form = new IPF_Auth_Forms_Login(array('next'=>$success_url));
+ $context = array(
+ 'page_title' => __('IPF Administration'),
+ 'form' => $form,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/login.html', $context, $request);
+}
+
+function IPF_Admin_Views_Logout($request, $match){
+ $request->user = new User();
+ $request->session->clear();
+ $request->session->setData('logout_time', gmdate('Y-m-d H:i:s'));
+
+ $context = array(
+ 'page_title' => __('IPF Administration'),
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/logout.html', $context, $request);
+}
--- /dev/null
+<?php
+
+abstract class IPF_Application{
+
+ protected $models = array();
+ protected $name = null;
+
+ public function __construct($data){
+ $this->setName();
+ if (array_key_exists('models',$data)){
+ foreach($data['models'] as &$modelname){
+ if (!IPF_Utils::isValidName($modelname))
+ throw new IPF_Exception_Panic("Model name \"$name\" is incorrect");
+ $this->models[] = $modelname;
+ }
+ }
+ }
+
+ protected function setName(){
+ $this->name = str_replace('_App', '', get_class($this));
+ if (strpos($this->name,'IPF_')===0)
+ $this->path = IPF::get('ipf_path').DIRECTORY_SEPARATOR.'ipf'.DIRECTORY_SEPARATOR.strtolower(str_replace('_',DIRECTORY_SEPARATOR,str_replace('IPF_','',$this->name)));
+ else
+ $this->path = IPF::get('project_path').DIRECTORY_SEPARATOR.strtolower(str_replace('_',DIRECTORY_SEPARATOR,$this->name));
+ $this->path .= DIRECTORY_SEPARATOR;
+ }
+
+ public function generateSql(){
+ if (count($this->models)==0)
+ return;
+ return IPF_ORM::generateSqlFromModels($this->path.'models');
+ }
+
+ public function modelList(){
+ return $this->models;
+ }
+
+ public function getName(){
+ return $this->name;
+ }
+
+ public function getLabel(){
+ return str_replace('ipf_','',strtolower($this->name));
+ }
+
+
+ public function getTitle(){
+ return $this->name;
+ }
+
+ public function getSlug(){
+ $e = explode('_',$this->name);
+ return strtolower($e[count($e)-1]);
+ }
+
+ public function createTablesFromModels(){
+ if (count($this->models)==0)
+ return;
+ return IPF_ORM::createTablesFromModels($this->path.'models');
+ }
+
+ public function generateModels(){
+ //if (count($this->models)==0)
+ // return;
+ IPF_ORM::generateModelsFromYaml($this->path.'models.yml', $this->path.'models');
+ }
+
+ public function loadModels(){
+ if (count($this->models)==0)
+ return;
+ IPF_ORM::loadModels($this->path.'models');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Auth_App extends IPF_Application{
+ public function __construct(){
+ parent::__construct(array(
+ 'models'=>array('User','Role')
+ ));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Auth_Forms_ChangePassword extends IPF_Form{
+
+ function initFields($extra=array())
+ {
+ $this->fields['password1'] = new IPF_Form_Field_Varchar(array('required'=>true,'widget'=>'IPF_Form_Widget_PasswordInput'));
+ $this->fields['password2'] = new IPF_Form_Field_Varchar(array('required'=>true,'widget'=>'IPF_Form_Widget_PasswordInput','help_text'=>'Enter the same password as above, for verification.'));
+ }
+
+ function isValid(){
+ $ok = parent::isValid();
+ if ($ok===true){
+ if ($this->cleaned_data['password1']!=$this->cleaned_data['password2']){
+ $this->is_valid = false;
+ $this->errors['password2'][] = "The two password fields didn't match.";
+ return false;
+ }
+ }
+ return $ok;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Auth_Forms_Login extends IPF_Form{
+ function initFields($extra=array()){
+ $this->fields['username'] = new IPF_Form_Field_Varchar(array('required'=>true));
+ $this->fields['password'] = new IPF_Form_Field_Varchar(array('required'=>true,'widget'=>'IPF_Form_Widget_PasswordInput'));
+ $this->fields['next'] = new IPF_Form_Field_Varchar(array('required'=>false,'widget'=>'IPF_Form_Widget_HiddenInput'));
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Auth_Forms_UserCreation extends IPF_Form_Model{
+ function __construct($data=null, $extra=array(), $label_suffix=null){
+ $extra['model'] = new User();
+ parent::__construct($data, $extra, $label_suffix);
+ }
+
+ function fields(){
+ return array('username','password1','password2','email','first_name','last_name','is_active','is_staff','is_superuser');
+ }
+
+ function addDBField($name,$col){
+ parent::addDBField($name, $col);
+ if ($name=='username')
+ $this->fields['username']->help_text = 'Required. 32 characters or fewer. Alphanumeric characters only (letters, digits and underscores).';
+ }
+
+ function add__password1__field(){
+ $this->fields['password1'] = new IPF_Form_Field_Varchar(array('label'=>'Password','required'=>true,'max_length'=>32,'widget'=>'IPF_Form_Widget_PasswordInput'));
+ }
+
+ function add__password2__field(){
+ $this->fields['password2'] = new IPF_Form_Field_Varchar(array('label'=>'Password (again)','required'=>true,'max_length'=>32,'widget'=>'IPF_Form_Widget_PasswordInput','help_text'=>'Enter the same password as above, for verification.'));
+ }
+
+ function isValid(){
+ $ok = parent::isValid();
+ if ($ok===true){
+ if ($this->cleaned_data['password1']!=$this->cleaned_data['password2']){
+ $this->is_valid = false;
+ $this->errors['password2'][] = "The two password fields didn't match.";
+ return false;
+ }
+ }
+ return $ok;
+ }
+}
--- /dev/null
+---
+User:
+ tableName: auth_user
+ actAs: [Timestampable]
+ columns:
+ username:
+ type: string(32)
+ notblank: true
+ notnull: true
+ unique: true
+ password:
+ type: string(128)
+ notblank: true
+ notnull: true
+ first_name: string(32)
+ last_name: string(32)
+ email:
+ type: string(200)
+ email: true
+ notnull: true
+ notblank: true
+ unique: true
+ is_staff:
+ type: boolean
+ notnull: true
+ default: false
+ is_active:
+ type: boolean
+ notnull: true
+ default: false
+ is_superuser:
+ type: boolean
+ notnull: true
+ default: false
+ last_login:
+ type: timestamp
+ relations:
+ Roles:
+ class: Role
+ refClass: UserRole
+ foreignAlias: Users
+ Permissions:
+ class: Permission
+ refClass: UserPermission
+ foreignAlias: Users
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
+Role:
+ tableName: auth_role
+ columns:
+ name:
+ unique: true
+ type: string(255)
+ relations:
+ Permissions:
+ class: Permissions
+ refClass: RolePermission
+ foreignAlias: Roles
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
+
+
+Permission:
+ tableName: auth_permission
+ columns:
+ name:
+ unique: true
+ type: string(255)
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
+
+RolePermission:
+ tableName: auth_role_permission
+ columns:
+ role_id:
+ type: integer
+ primary: true
+ permission_id:
+ type: integer
+ primary: true
+ relations:
+ Role:
+ Permission:
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
+
+UserRole:
+ tableName: auth_user_role
+ columns:
+ user_id:
+ type: integer
+ primary: true
+ role_id:
+ type: integer
+ primary: true
+ relations:
+ User:
+ Role:
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
+
+UserPermission:
+ tableName: auth_user_permission
+ columns:
+ user_id:
+ type: integer
+ primary: true
+ permission_id:
+ type: integer
+ primary: true
+ relations:
+ User:
+ Permission:
+ options:
+ type: INNODB
+ collate: utf8_unicode_ci
+ charset: utf8
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+class Permission extends BasePermission
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+class Role extends BaseRole
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+class RolePermission extends BaseRolePermission
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class AdminUser extends IPF_Admin_Model{
+ public function list_display(){return array('username', 'email', 'is_active', 'is_staff', 'is_superuser', 'created_at');}
+ public function fields(){return array('username','password','email', 'first_name', 'last_name', 'is_active', 'is_staff', 'is_superuser');}
+
+ protected function _setupForm(&$form){
+ $form->fields['username']->help_text = 'Required. 32 characters or fewer. Alphanumeric characters only (letters, digits and underscores).';
+ $form->fields['password']->help_text = "Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>.";
+ }
+
+ public function AddItem($request, $lapp, $lmodel){
+ $model = new $this->modelName();
+ if ($request->method == 'POST'){
+ $form = new IPF_Auth_Forms_UserCreation($request->POST);
+ if ($form->isValid()) {
+ $user = User::createUser(
+ $form->cleaned_data['username'],
+ $form->cleaned_data['password1'],
+ $form->cleaned_data['email'],
+ $form->cleaned_data['first_name'],
+ $form->cleaned_data['last_name'],
+ $form->cleaned_data['is_active'],
+ $form->cleaned_data['is_staff'],
+ $form->cleaned_data['is_superuser']
+ );
+ $url = IPF_HTTP_URL_urlForView('IPF_Admin_Views_ListItems', array($lapp, $lmodel));
+ return new IPF_HTTP_Response_Redirect($url);
+ }
+ }
+ else
+ $form = new IPF_Auth_Forms_UserCreation();
+ $context = array(
+ 'page_title'=>'Add '.$this->modelName,
+ 'classname'=>$this->modelName,
+ 'form'=>$form,
+ 'lapp'=>$lapp,
+ 'lmodel'=>$lmodel,
+ );
+ return IPF_Shortcuts::RenderToResponse('admin/add.html', $context, $request);
+ }
+}
+
+
+class User extends BaseUser
+{
+ const UNUSABLE_PASSWORD = '!';
+ public $session_key = 'IPF_User_auth';
+
+ public function __toString() {
+ $s = $this->username;
+ if ($s===null)
+ return 'Anonymous';
+ return $s;
+ }
+
+ static function createUser($username, $password=null, $email=null, $first_name=null, $last_name=null, $is_active=false, $is_staff=false, $is_superuser=false){
+ $user = new User();
+ $user->username = $username;
+
+ if (trim($email)=='')
+ $user->email = null;
+ else
+ $user->email = $email;
+
+ $user->first_name = $first_name;
+ $user->last_name = $last_name;
+ $user->is_active = $is_active;
+ $user->is_staff = $is_staff;
+ $user->is_superuser = $is_superuser;
+
+ if ($password!==null)
+ $user->setPassword($password);
+ else
+ $user->setUnusablePassword();
+
+ try {
+ $user->save();
+ } catch(IPF_ORM_Exception_Validator $e) {
+ //print_r($e);
+ // Note: you could also use $e->getInvalidRecords(). The direct way
+ // used here is just more simple when you know the records you're dealing with.
+ $userErrors = $user->getErrorStack();
+ //$emailErrors = $user->email->getErrorStack();
+ // Inspect user errors
+ foreach($userErrors as $fieldName => $errorCodes) {
+ echo "Error:".$fieldName;
+ print_r($errorCodes);
+ }
+ }
+ return $user;
+ }
+
+ function setUnusablePassword(){
+ $this->password = UNUSABLE_PASSWORD;
+ }
+
+ function setPassword($raw_password){
+ $salt = IPF_Utils::randomString(5);
+ $this->password = 'sha1:'.$salt.':'.sha1($salt.$raw_password);
+ }
+
+ function checkPassword($password){
+ if ( ($this->password=='') || ($this->password==User::UNUSABLE_PASSWORD) )
+ return false;
+ list($algo, $salt, $hash) = split(':', $this->password);
+ if ($hash == $algo($salt.$password))
+ return true;
+ else
+ return false;
+ }
+
+ function isAnonymous()
+ {
+ if (0===(int)$this->id)
+ return true;
+ return false;
+ }
+
+ function checkCreditentials($username, $password)
+ {
+ $user = $this->getTable()->findOneByUsername($username);
+ if ($user === false) {
+ return false;
+ }
+ if ($user->is_active and $user->checkPassword($password)) {
+ return $user;
+ }
+ return false;
+ }
+}
+
+IPF_Admin_Model::register(User,AdminUser);
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+class UserPermission extends BaseUserPermission
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+class UserRole extends BaseUserRole
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BasePermission extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('auth_permission');
+ $this->hasColumn('name', 'string', 255, array('unique' => true, 'type' => 'string', 'length' => '255'));
+
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $this->hasMany('User as Users', array('refClass' => 'UserPermission',
+ 'local' => 'permission_id',
+ 'foreign' => 'user_id'));
+
+ $this->hasMany('RolePermission', array('local' => 'id',
+ 'foreign' => 'permission_id'));
+
+ $this->hasMany('UserPermission', array('local' => 'id',
+ 'foreign' => 'permission_id'));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseRole extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('auth_role');
+ $this->hasColumn('name', 'string', 255, array('unique' => true, 'type' => 'string', 'length' => '255'));
+
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $this->hasMany('User as Users', array('refClass' => 'UserRole',
+ 'local' => 'role_id',
+ 'foreign' => 'user_id'));
+
+ $this->hasMany('RolePermission', array('local' => 'id',
+ 'foreign' => 'role_id'));
+
+ $this->hasMany('UserRole', array('local' => 'id',
+ 'foreign' => 'role_id'));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseRolePermission extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('auth_role_permission');
+ $this->hasColumn('role_id', 'integer', null, array('type' => 'integer', 'primary' => true));
+ $this->hasColumn('permission_id', 'integer', null, array('type' => 'integer', 'primary' => true));
+
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $this->hasOne('Role', array('local' => 'role_id',
+ 'foreign' => 'id'));
+
+ $this->hasOne('Permission', array('local' => 'permission_id',
+ 'foreign' => 'id'));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseUser extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('auth_user');
+ $this->hasColumn('username', 'string', 32, array('type' => 'string', 'notblank' => true, 'notnull' => true, 'unique' => true, 'length' => '32'));
+ $this->hasColumn('password', 'string', 128, array('type' => 'string', 'notblank' => true, 'notnull' => true, 'length' => '128'));
+ $this->hasColumn('first_name', 'string', 32, array('type' => 'string', 'length' => '32'));
+ $this->hasColumn('last_name', 'string', 32, array('type' => 'string', 'length' => '32'));
+ $this->hasColumn('email', 'string', 200, array('type' => 'string', 'email' => true, 'notnull' => true, 'notblank' => true, 'unique' => true, 'length' => '200'));
+ $this->hasColumn('is_staff', 'boolean', null, array('type' => 'boolean', 'notnull' => true, 'default' => false));
+ $this->hasColumn('is_active', 'boolean', null, array('type' => 'boolean', 'notnull' => true, 'default' => false));
+ $this->hasColumn('is_superuser', 'boolean', null, array('type' => 'boolean', 'notnull' => true, 'default' => false));
+ $this->hasColumn('last_login', 'timestamp', null, array('type' => 'timestamp'));
+
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $this->hasMany('Role as Roles', array('refClass' => 'UserRole',
+ 'local' => 'user_id',
+ 'foreign' => 'role_id'));
+
+ $this->hasMany('Permission as Permissions', array('refClass' => 'UserPermission',
+ 'local' => 'user_id',
+ 'foreign' => 'permission_id'));
+
+ $this->hasMany('UserRole', array('local' => 'id',
+ 'foreign' => 'user_id'));
+
+ $this->hasMany('UserPermission', array('local' => 'id',
+ 'foreign' => 'user_id'));
+
+ $timestampable0 = new IPF_ORM_Template_Timestampable();
+ $this->actAs($timestampable0);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseUserPermission extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('auth_user_permission');
+ $this->hasColumn('user_id', 'integer', null, array('type' => 'integer', 'primary' => true));
+ $this->hasColumn('permission_id', 'integer', null, array('type' => 'integer', 'primary' => true));
+
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $this->hasOne('User', array('local' => 'user_id',
+ 'foreign' => 'id'));
+
+ $this->hasOne('Permission', array('local' => 'permission_id',
+ 'foreign' => 'id'));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseUserRole extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('auth_user_role');
+ $this->hasColumn('user_id', 'integer', null, array('type' => 'integer', 'primary' => true));
+ $this->hasColumn('role_id', 'integer', null, array('type' => 'integer', 'primary' => true));
+
+ $this->option('type', 'INNODB');
+ $this->option('collate', 'utf8_unicode_ci');
+ $this->option('charset', 'utf8');
+ }
+
+ public function setUp()
+ {
+ $this->hasOne('User', array('local' => 'user_id',
+ 'foreign' => 'id'));
+
+ $this->hasOne('Role', array('local' => 'role_id',
+ 'foreign' => 'id'));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Cli{
+
+ protected $commands;
+
+ public function __construct(){
+ $this->commands = array('help','sql','buildmodels','syncdb');
+ }
+
+ protected function usage(&$args){
+ print "Type 'php index.php help' for usage.\n";
+ }
+
+ protected function main_help(&$args){
+ print "php index.php <subcommand> [options] [args]\n";
+ print "Available subcommands:\n";
+ foreach ($this->commands as &$command)
+ print " $command\n";
+ }
+
+ protected function help(&$args){
+ if (count($args)==2)
+ $this->main_help(&$args);
+ }
+
+ protected function sql(&$args){
+ print "Show All Sql DDL From Model Classes\n";
+ print IPF_Project::getInstance()->generateSql();
+ }
+
+ protected function syncdb(&$args){
+ print "Create Tables From Model Classes\n";
+ IPF_Project::getInstance()->createTablesFromModels();
+ }
+
+ protected function buildmodels(&$args){
+ print "Build All Model Classses\n";
+ IPF_Project::getInstance()->generateModels();
+ }
+
+ public function run(){
+
+ print "IPF command line tool. Version: ".IPF_Version::$name."\n";
+ print "Project config: ".IPF::get('settings_file')."\n";
+
+ $opt = new IPF_Getopt();
+ //$z = $opt->getopt2($opt->readPHPArgv(), array('s',)); //, array('s',));
+ $args = $opt->readPHPArgv();
+ if (count($args)==1)
+ {
+ $this->usage(&$args);
+ return;
+ }
+
+ if (in_array($args[1],$this->commands))
+ {
+ eval('$this->'.$args[1].'(&$args);');
+ return;
+ }
+
+ print "Unknown command: '".$args[1]."'\n";
+ $this->usage(&$args);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+function IPF_Context_Auth($request)
+{
+ return array('user' => $request->user);
+}
+
+function IPF_Context_Media($request)
+{
+ return array('MEDIA_URL' => IPF::get('media_url'));
+}
+
+function IPF_Context_AdminMedia($request)
+{
+ return array('ADMIN_MEDIA_URL' => IPF::get('admin_media_url'));
+}
+
+function IPF_Context_Version($request)
+{
+ return array('IPF_VER' => IPF_Version::$name);
+}
--- /dev/null
+<?php
+
+/*
+Usage:
+
+class Enum_Colors extends IPF_Enum {
+ const RED = 'F00';
+ const GREEN = '0F0';
+ const BLUE = '00F';
+}
+
+function setColor( Enum_Colors $color ) {
+ echo $color;
+}
+setColor( new Enum_Colors( 'GREEN' ) ); // will pass
+setColor( '0F0' ); // won't pass
+Enum_Colors::RED == new Enum_Colors( 'GREEN' ); // FALSE
+Enum_Colors::RED == new Enum_Colors( 'RED' ); // TRUE
+
+*/
+
+abstract class IPF_Enum {
+ private $current_val;
+
+ final public function __construct( $type ) {
+ $class_name = get_class( $this );
+
+ $type = strtoupper( $type );
+ if ( ! constant( "{$class_name}::{$type}" ) ) {
+ throw new IPF_Exception_Enum( 'Not forund property "'.$type.'" in enum "'.$class_name.'"' );
+ }
+ $this->current_val = constant( "{$class_name}::{$type}" );
+ }
+
+ final public function __toString() {
+ return $this->current_val;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Exception extends Exception{}
--- /dev/null
+<?php
+
+class IPF_Exception_Form extends IPF_Exception{}
--- /dev/null
+<?php
+
+class IPF_Exception_Panic extends IPF_Exception{}
--- /dev/null
+<?php
+
+class IPF_Exception_Settings extends IPF_Exception{}
--- /dev/null
+<?php
+
+class IPF_Exception_Template extends IPF_Exception{}
--- /dev/null
+<?php
+
+class IPF_Form implements Iterator
+{
+ public $fields = array();
+
+ public $prefix = '';
+ public $id_fields = 'id_%s';
+ public $data = array();
+ public $cleaned_data = array();
+ public $errors = array();
+ public $is_bound = false;
+ public $f = null;
+ public $label_suffix = ':';
+
+ protected $is_valid = null;
+
+ function __construct($data=null, $extra=array(), $label_suffix=null)
+ {
+ if ($data !== null) {
+ $this->data = $data;
+ $this->is_bound = true;
+ }
+ if ($label_suffix !== null) $this->label_suffix = $label_suffix;
+
+ $this->initFields($extra);
+ $this->f = new IPF_Form_FieldProxy($this);
+ }
+
+ function initFields($extra=array())
+ {
+ throw new IPF_Exception('Definition of the fields not implemented.');
+ }
+
+ function addPrefix($field_name)
+ {
+ if ('' !== $this->prefix) {
+ return $this->prefix.'-'.$field_name;
+ }
+ return $field_name;
+ }
+
+ function isValid()
+ {
+ if ($this->is_valid !== null) {
+ return $this->is_valid;
+ }
+ $this->cleaned_data = array();
+ $this->errors = array();
+ $form_methods = get_class_methods($this);
+ foreach ($this->fields as $name=>$field) {
+ $value = $field->widget->valueFromFormData($this->addPrefix($name),
+ $this->data);
+ try {
+ $value = $field->clean($value);
+ $this->cleaned_data[$name] = $value;
+ if (in_array('clean_'.$name, $form_methods)) {
+ $m = 'clean_'.$name;
+ $value = $this->$m();
+ $this->cleaned_data[$name] = $value;
+ }
+ } catch (IPF_Exception_Form $e) {
+ if (!isset($this->errors[$name])) $this->errors[$name] = array();
+ $this->errors[$name][] = $e->getMessage();
+ if (isset($this->cleaned_data[$name])) {
+ unset($this->cleaned_data[$name]);
+ }
+ }
+ }
+ try {
+ $this->cleaned_data = $this->clean();
+ } catch (IPF_Exception_Form $e) {
+ if (!isset($this->errors['__all__'])) $this->errors['__all__'] = array();
+ $this->errors['__all__'][] = $e->getMessage();
+ }
+ if (empty($this->errors)) {
+ $this->is_valid = true;
+ return true;
+ }
+ // as some errors, we do not have cleaned data available.
+ $this->cleaned_data = array();
+ $this->is_valid = false;
+ return false;
+ }
+
+ public function clean()
+ {
+ return $this->cleaned_data;
+ }
+
+ public function initial($name)
+ {
+ if (isset($this->fields[$name])) {
+ return $this->fields[$name]->initial;
+ }
+ return '';
+ }
+
+ public function render_top_errors()
+ {
+ $top_errors = (isset($this->errors['__all__'])) ? $this->errors['__all__'] : array();
+ array_walk($top_errors, 'IPF_Form_htmlspecialcharsArray');
+ return new IPF_Template_SafeString(IPF_Form_renderErrorsAsHTML($top_errors), true);
+ }
+
+ public function get_top_errors()
+ {
+ return (isset($this->errors['__all__'])) ? $this->errors['__all__'] : array();
+ }
+
+ protected function htmlOutput($normal_row, $error_row, $row_ender,
+ $help_text_html, $errors_on_separate_row)
+ {
+ $top_errors = (isset($this->errors['__all__'])) ? $this->errors['__all__'] : array();
+ array_walk($top_errors, 'IPF_Form_htmlspecialcharsArray');
+ $output = array();
+ $hidden_fields = array();
+ foreach ($this->fields as $name=>$field) {
+ $bf = new IPF_Form_BoundField($this, $field, $name);
+ $bf_errors = $bf->errors;
+ array_walk($bf_errors, 'IPF_Form_htmlspecialcharsArray');
+ if ($field->widget->is_hidden) {
+ foreach ($bf_errors as $_e) {
+ $top_errors[] = sprintf(__('(Hidden field %1$s) %2$s'),
+ $name, $_e);
+ }
+ $hidden_fields[] = $bf; // Not rendered
+ } else {
+ if ($errors_on_separate_row and count($bf_errors)) {
+ $output[] = sprintf($error_row, IPF_Form_renderErrorsAsHTML($bf_errors));
+ }
+ if (strlen($bf->label) > 0) {
+ $label = htmlspecialchars($bf->label, ENT_COMPAT, 'UTF-8');
+ if ($this->label_suffix) {
+ if (!in_array(mb_substr($label, -1, 1),
+ array(':','?','.','!'))) {
+ $label .= $this->label_suffix;
+ }
+ }
+ if ($field->required)
+ $label_attrs = array('class'=>'required');
+ else
+ $label_attrs = array();
+ $label = $bf->labelTag($label,$label_attrs);
+ } else {
+ $label = '';
+ }
+ if ($bf->help_text) {
+ // $bf->help_text can contains HTML and is not
+ // escaped.
+ $help_text = sprintf($help_text_html, $bf->help_text);
+ } else {
+ $help_text = '';
+ }
+ $errors = '';
+ if (!$errors_on_separate_row and count($bf_errors)) {
+ $errors = IPF_Form_renderErrorsAsHTML($bf_errors);
+ }
+ $output[] = sprintf($normal_row, $errors, $label,
+ $bf->render_w(), $help_text);
+ }
+ }
+ if (count($top_errors)) {
+ $errors = sprintf($error_row,
+ IPF_Form_renderErrorsAsHTML($top_errors));
+ array_unshift($output, $errors);
+ }
+ if (count($hidden_fields)) {
+ $_tmp = '';
+ foreach ($hidden_fields as $hd) {
+ $_tmp .= $hd->render_w();
+ }
+ if (count($output)) {
+ $last_row = array_pop($output);
+ $last_row = substr($last_row, 0, -strlen($row_ender)).$_tmp
+ .$row_ender;
+ $output[] = $last_row;
+ } else {
+ $output[] = $_tmp;
+ }
+ }
+ return new IPF_Template_SafeString(implode("\n", $output), true);
+ }
+
+ public function render_p()
+ {
+ return $this->htmlOutput('<p>%1$s%2$s %3$s%4$s</p>', '%s', '</p>',
+ ' %s', true);
+ }
+
+ public function render_ul()
+ {
+ return $this->htmlOutput('<li>%1$s%2$s %3$s%4$s</li>', '<li>%s</li>',
+ '</li>', ' %s', false);
+ }
+
+ public function render_table()
+ {
+ return $this->htmlOutput('<tr><th>%2$s</th><td>%1$s%3$s%4$s</td></tr>',
+ '<tr><td colspan="2">%s</td></tr>',
+ '</td></tr>', '<br /><span class="helptext">%s</span>', false);
+ }
+
+ function __get($prop)
+ {
+ if (!in_array($prop, array('render_p', 'render_ul', 'render_table', 'render_top_errors', 'get_top_errors'))) {
+ return $this->$prop;
+ }
+ return $this->$prop();
+ }
+
+ public function field($key)
+ {
+ return new IPF_Form_BoundField($this, $this->fields[$key], $key);
+
+ }
+
+ public function current()
+ {
+ $field = current($this->fields);
+ $name = key($this->fields);
+ return new IPF_Form_BoundField($this, $field, $name);
+ }
+
+ public function key()
+ {
+ return key($this->fields);
+ }
+
+ public function next()
+ {
+ next($this->fields);
+ }
+
+ public function rewind()
+ {
+ reset($this->fields);
+ }
+
+ public function valid()
+ {
+ return (false !== current($this->fields));
+ }
+
+}
+
+function IPF_Form_htmlspecialcharsArray(&$item, $key)
+{
+ $item = htmlspecialchars($item, ENT_COMPAT, 'UTF-8');
+}
+
+function IPF_Form_renderErrorsAsHTML($errors)
+{
+ $tmp = array();
+ foreach ($errors as $err) {
+ $tmp[] = '<li>'.$err.'</li>';
+ }
+ return '<ul class="errorlist">'.implode("\n", $tmp).'</ul>';
+}
--- /dev/null
+<?php
+
+class IPF_Form_BoundField
+{
+ public $form = null;
+ public $field = null;
+ public $name = null;
+ public $html_name = null;
+ public $label = null;
+ public $help_text = null;
+ public $errors = array();
+
+ public function __construct($form, $field, $name)
+ {
+ $this->form = $form;
+ $this->field = $field;
+ $this->name = $name;
+ $this->html_name = $this->form->addPrefix($name);
+ if ($this->field->label == '') {
+ $this->label = mb_ereg_replace('/\_/', '/ /', mb_ucfirst($name));
+ } else {
+ $this->label = $this->field->label;
+ }
+ $this->help_text = ($this->field->help_text) ? $this->field->help_text : '';
+ if (isset($this->form->errors[$name])) {
+ $this->errors = $this->form->errors[$name];
+ }
+ }
+
+ public function render_w($widget=null, $attrs=array())
+ {
+ if ($widget === null) {
+ $widget = $this->field->widget;
+ }
+ $id = $this->autoId();
+ if ($id and !array_key_exists('id', $attrs)
+ and !array_key_exists('id', $widget->attrs)) {
+ $attrs['id'] = $id;
+ }
+ if (!$this->form->is_bound) {
+ $data = $this->form->initial($this->name);
+ } else {
+ $data = $this->field->widget->valueFromFormData($this->html_name, $this->form->data);
+ }
+ return $widget->render($this->html_name, $data, $attrs);
+ }
+
+ public function labelTag($contents=null, $attrs=array())
+ {
+ $contents = ($contents) ? $contents : htmlspecialchars($this->label);
+ $widget = $this->field->widget;
+ $id = (isset($widget->attrs['id'])) ? $widget->attrs['id'] : $this->autoId();
+ $_tmp = array();
+ foreach ($attrs as $attr=>$val) {
+ $_tmp[] = $attr.'="'.$val.'"';
+ }
+ if (count($_tmp)) {
+ $attrs = ' '.implode(' ', $_tmp);
+ } else {
+ $attrs = '';
+ }
+ return new IPF_Template_SafeString(sprintf('<label for="%s"%s>%s</label>',
+ $widget->idForLabel($id), $attrs, $contents), true);
+ }
+
+ public function autoId()
+ {
+ $id_fields = $this->form->id_fields;
+ if (false !== strpos($id_fields, '%s')) {
+ return sprintf($id_fields, $this->html_name);
+ } elseif ($id_fields) {
+ return $this->html_name;
+ }
+ return '';
+ }
+
+ public function fieldErrors()
+ {
+ IPF::loadFunction('IPF_Form_renderErrorsAsHTML');
+ return new IPF_Template_SafeString(IPF_Form_renderErrorsAsHTML($this->errors), true);
+ }
+
+ public function __get($prop)
+ {
+ if (!in_array($prop, array('labelTag', 'fieldErrors', 'render_w'))) {
+ return $this->$prop;
+ }
+ return $this->$prop();
+ }
+
+ public function __toString()
+ {
+ return (string)$this->render_w();
+ }
+}
+
+if (!function_exists('mb_ucfirst')) {
+ function mb_ucfirst($str) {
+ return mb_strtoupper(mb_substr($str, 0, 1)).mb_substr($str, 1);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_DB
+{
+ public $type = '';
+ public $column = '';
+ public $value;
+ public $extra = array();
+ public $methods = array();
+
+ function __construct($value='', $column='', $extra=array())
+ {
+ $this->value = $value;
+ $this->column = $column;
+ $this->extra = array_merge($this->extra, $extra);
+ }
+
+ function formField($def, $form_field='IPF_Form_Field_Varchar')
+ {
+ $defaults = array('required' => !$def['blank'],
+ 'label' => IPF_Utils::humanTitle($def['verbose']),
+ 'help_text' => $def['help_text']);
+ unset($def['blank'], $def['verbose'], $def['help_text']);
+ if (isset($def['default'])) {
+ $defaults['initial'] = $def['default'];
+ unset($def['default']);
+ }
+ if (isset($def['choices'])) {
+ $defaults['widget'] = 'IPF_Form_Widget_SelectInput';
+ if (isset($def['widget_attrs'])) {
+ $def['widget_attrs']['choices'] = $def['choices'];
+ } else {
+ $def['widget_attrs'] = array('choices' => $def['choices']);
+ }
+ }
+ foreach (array_keys($def) as $key) {
+ if (!in_array($key, array('max_length','widget', 'label', 'required',
+ 'initial', 'choices', 'widget_attrs'))) {
+ unset($def[$key]);
+ }
+ }
+ $params = array_merge($defaults, $def);
+ return new $form_field($params);
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Form_DB_Boolean extends IPF_Form_DB{
+ public $type = 'boolean';
+ function formField($def, $form_field='IPF_Form_Field_Boolean'){
+ return parent::formField($def, $form_field);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_DB_Email extends IPF_Form_DB{
+ public $type = 'varchar';
+ public $extra = array('size' => 200);
+
+ function formField($def, $form_field='IPF_Form_Field_Email'){
+ return parent::formField($def, $form_field);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_DB_Integer extends IPF_Form_DB{
+
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public $max = null;
+ public $min = null;
+
+ public function clean($value)
+ {
+ parent::clean($value);
+ if (in_array($value, $this->empty_values)) {
+ $value = '';
+ }
+ if (is_array($value)) {
+ reset($value);
+ while (list($i, $val) = each($value)) {
+ if (!preg_match('/[0-9]+/', $val)) {
+ throw new IPF_Exception_Form(__('The value must be an integer.'));
+ }
+ $this->checkMinMax($val);
+ $value[$i] = (int) $val;
+ }
+ reset($value);
+ return $value;
+ } else {
+ if (!preg_match('/[0-9]+/', $value)) {
+ throw new IPF_Exception_Form(__('The value must be an integer.'));
+ }
+ $this->checkMinMax($value);
+ }
+ return (int) $value;
+ }
+
+ protected function checkMinMax($value)
+ {
+ if ($this->max !== null and $value > $this->max) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure that this value is not greater than %1$d.'), $this->max));
+ }
+ if ($this->min !== null and $value < $this->min) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure that this value is not lower than %1$d.'), $this->min));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_DB_String extends IPF_Form_DB
+{
+ public $type = 'varchar';
+}
--- /dev/null
+<?php
+
+class IPF_Form_DB_Timestamp extends IPF_Form_DB{
+ public $type = 'datetime';
+ function formField($def, $form_field='IPF_Form_Field_Datetime'){
+ return parent::formField($def, $form_field);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field
+{
+ public $class = 'IPF_Form_Field';
+
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public $label = '';
+ public $required = false;
+ public $help_text = '';
+ public $initial = '';
+ public $choices = null;
+
+ public $hidden_widget = 'IPF_Form_Widget_HiddenInput';
+ public $value = ''; /**< Current value of the field. */
+ protected $empty_values = array('', null, array());
+
+ function __construct($params=array())
+ {
+ $default = array();
+ foreach ($params as $key=>$in) {
+ if ($key !== 'widget_attrs')
+ $default[$key] = $this->$key;
+ }
+ $m = array_merge($default, $params);
+ foreach ($params as $key=>$in) {
+ if ($key !== 'widget_attrs')
+ $this->$key = $m[$key];
+ }
+ $widget_name = $this->widget;
+ if (isset($params['widget_attrs'])) {
+ $attrs = $params['widget_attrs'];
+ } else {
+ $attrs = array();
+ }
+ $widget = new $widget_name($attrs);
+ $attrs = $this->widgetAttrs($widget);
+ if (count($attrs)) {
+ $widget->attrs = array_merge($widget->attrs, $attrs);
+ }
+ $this->widget = $widget;
+ }
+
+ function clean($value)
+ {
+ if ($this->required and in_array($value, $this->empty_values)) {
+ throw new IPF_Exception_Form(__('This field is required.'));
+ }
+ return $value;
+ }
+
+ public function widgetAttrs($widget)
+ {
+ return array();
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Form_Field_Boolean extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_CheckboxInput';
+
+ public function clean($value)
+ {
+ if (in_array($value, array('on', 'y', '1', 1, true))) {
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_Date extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public $input_formats = array(
+ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', // 2006-10-25, 10/25/2006, 10/25/06
+ '%b %d %Y', '%b %d, %Y', // 'Oct 25 2006', 'Oct 25, 2006'
+ '%d %b %Y', '%d %b, %Y', // '25 Oct 2006', '25 Oct, 2006'
+ '%B %d %Y', '%B %d, %Y', // 'October 25 2006', 'October 25, 2006'
+ '%d %B %Y', '%d %B, %Y', // '25 October 2006', '25 October, 2006'
+ );
+
+ public function clean($value)
+ {
+ parent::clean($value);
+ foreach ($this->input_formats as $format) {
+ if (false !== ($date = strptime($value, $format))) {
+ $day = str_pad($date['tm_mday'], 2, '0', STR_PAD_LEFT);
+ $month = str_pad($date['tm_mon']+1, 2, '0', STR_PAD_LEFT);
+ $year = str_pad($date['tm_year']+1900, 4, '0', STR_PAD_LEFT);
+ return $year.'-'.$month.'-'.$day;
+ }
+ }
+ throw new IPF_Exception_Form(__('Enter a valid date.'));
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_Datetime extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_DatetimeInput';
+ public $input_formats = array(
+ '%Y-%m-%d %H:%M:%S', // '2006-10-25 14:30:59'
+ '%Y-%m-%d %H:%M', // '2006-10-25 14:30'
+ '%Y-%m-%d', // '2006-10-25'
+ '%m/%d/%Y %H:%M:%S', // '10/25/2006 14:30:59'
+ '%m/%d/%Y %H:%M', // '10/25/2006 14:30'
+ '%m/%d/%Y', // '10/25/2006'
+ '%m/%d/%y %H:%M:%S', // '10/25/06 14:30:59'
+ '%m/%d/%y %H:%M', // '10/25/06 14:30'
+ '%m/%d/%y', // '10/25/06'
+ );
+
+ public function clean($value)
+ {
+ parent::clean($value);
+ $out = null;
+ foreach ($this->input_formats as $format) {
+ if (false !== ($date = strptime($value, $format))) {
+ $day = str_pad($date['tm_mday'], 2, '0', STR_PAD_LEFT);
+ $month = str_pad($date['tm_mon']+1, 2, '0', STR_PAD_LEFT);
+ $year = str_pad($date['tm_year']+1900, 4, '0', STR_PAD_LEFT);
+ $h = str_pad($date['tm_hour'], 2, '0', STR_PAD_LEFT);
+ $m = str_pad($date['tm_min'], 2, '0', STR_PAD_LEFT);
+ $s = $date['tm_sec'];
+ if ($s > 59) $s=59;
+ $s = str_pad($s, 2, '0', STR_PAD_LEFT);
+ $out = $year.'-'.$month.'-'.$day.' '.$h.':'.$m.':'.$s;
+ break;
+ }
+ }
+ if ($out !== null) {
+ // We internally use GMT, so we convert it to a GMT date.
+ return gmdate('Y-m-d H:i:s', strtotime($out));
+ }
+ throw new IPF_Exception_Form(__('Enter a valid date/time.'));
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_Email extends IPF_Form_Field{
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public function clean($value){
+ parent::clean($value);
+ if (in_array($value, $this->empty_values))
+ return '';
+
+ if (!IPF_Utils::isEmail($value)) {
+ throw new IPF_Exception_Form(__('Enter a valid email address.'));
+ }
+ return $value;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_File extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_FileInput';
+ public $move_function = 'IPF_Form_Field_moveToUploadFolder';
+ public $max_size = 2097152; // 2MB
+ public $move_function_params = array();
+
+ function clean($value)
+ {
+ parent::clean($value);
+ $errors = array();
+ $no_files = false;
+ switch ($value['error']) {
+ case UPLOAD_ERR_OK:
+ break;
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ throw new IPF_Exception_Form(__('The uploaded file is too large. Reduce the size of the file and send it again.'));
+ break;
+ case UPLOAD_ERR_PARTIAL:
+ throw new IPF_Exception_Form(__('The upload did not complete. Please try to send the file again.'));
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ throw new IPF_Exception_Form(__('No files were uploaded. Please try to send the file again.'));
+ break;
+ case UPLOAD_ERR_NO_TMP_DIR:
+ case UPLOAD_ERR_CANT_WRITE:
+ throw new IPF_Exception_Form(__('The server has no temporary folder correctly configured to store the uploaded file.'));
+ break;
+ case UPLOAD_ERR_EXTENSION:
+ throw new IPF_Exception_Form(__('The uploaded file has been stopped by an extension.'));
+ break;
+ default:
+ throw new IPF_Exception_Form(__('An error occured when upload the file. Please try to send the file again.'));
+ }
+ if ($value['size'] > $this->max_size) {
+ throw new IPF_Exception_Form(sprintf(__('The uploaded file is to big (%1$s). Reduce the size to less than %2$s and try again.'),
+ IPF_Utils::prettySize($value['size']),
+ IPF_Utils::prettySize($this->max_size)));
+ }
+ IPF::loadFunction($this->move_function);
+ return call_user_func($this->move_function, $value, $this->move_function_params);
+ }
+}
+
+function IPF_Form_Field_moveToUploadFolder($value, $params=array())
+{
+ $name = IPF_Utils::cleanFileName($value['name']);
+ $upload_path = IPF::get('upload_path', '/tmp');
+ if (isset($params['upload_path'])) {
+ $upload_path = $params['upload_path'];
+ }
+ $dest = $upload_path.'/'.$name;
+ if (!@move_uploaded_file($value['tmp_name'], $dest)) {
+ throw new IPF_Exception_Form(__('An error occured when upload the file. Please try to send the file again.'));
+ }
+ @chmod($dest, 0666);
+ return $name;
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_Float extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public $max_value = null;
+ public $min_value = null;
+
+ public function clean($value)
+ {
+ parent::clean($value);
+ if (in_array($value, $this->empty_values)) {
+ $value = '';
+ }
+ $_value = $value;
+ $value = (float) $value;
+ if ((string) $value !== (string) $_value) {
+ throw new IPF_Exception_Form(__('Enter a number.'));
+ }
+ if ($this->max_value !== null and $this->max_value < $value) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure this value is less than or equal to %s.'), $this->max_value));
+ }
+ if ($this->min_value !== null and $this->min_value > $value) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure this value is greater than or equal to %s.'), $this->min_value));
+ }
+ return $value;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_Integer extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public $max = null;
+ public $min = null;
+
+ public function clean($value)
+ {
+ parent::clean($value);
+ if (in_array($value, $this->empty_values)) {
+ $value = '';
+ }
+ if (is_array($value)) {
+ reset($value);
+ while (list($i, $val) = each($value)) {
+ if (!preg_match('/[0-9]+/', $val)) {
+ throw new IPF_Exception_Form(__('The value must be an integer.'));
+ }
+ $this->checkMinMax($val);
+ $value[$i] = (int) $val;
+ }
+ reset($value);
+ return $value;
+ } else {
+ if (!preg_match('/[0-9]+/', $value)) {
+ throw new IPF_Exception_Form(__('The value must be an integer.'));
+ }
+ $this->checkMinMax($value);
+ }
+ return (int) $value;
+ }
+
+ protected function checkMinMax($value)
+ {
+ if ($this->max !== null and $value > $this->max) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure that this value is not greater than %1$d.'), $this->max));
+ }
+ if ($this->min !== null and $value < $this->min) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure that this value is not lower than %1$d.'), $this->min));
+ }
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Form_Field_Url extends IPF_Form_Field
+{
+ public $widget = 'IPF_Form_Widget_TextInput';
+
+ public function clean($value)
+ {
+ parent::clean($value);
+ if (in_array($value, $this->empty_values)) {
+ return '';
+ }
+ if (!IPF_Utils::isValidUrl($value)) {
+ throw new IPF_Exception_Form(__('Enter a valid address.'));
+ }
+ return $value;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Field_Varchar extends IPF_Form_Field{
+ public $widget = 'IPF_Form_Widget_TextInput';
+ public $max_length = null;
+ public $min_length = null;
+
+ public function clean($value){
+ parent::clean($value);
+ if (in_array($value, $this->empty_values)) {
+ $value = '';
+ }
+ $value_length = mb_strlen($value);
+ if ($this->max_length !== null and $value_length > $this->max_length) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure this value has at most %1$d characters (it has %2$d).'), $this->max_length, $value_length));
+ }
+ if ($this->min_length !== null and $value_length < $this->min_length) {
+ throw new IPF_Exception_Form(sprintf(__('Ensure this value has at least %1$d characters (it has %2$d).'), $this->min_length, $value_length));
+ }
+ return $value;
+ }
+
+ public function widgetAttrs($widget){
+ if ($this->max_length !== null and in_array(get_class($widget), array('IPF_Form_Widget_TextInput', 'IPF_Form_Widget_PasswordInput'))) {
+ return array('maxlength'=>$this->max_length);
+ }
+ return array();
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Form_FieldProxy
+{
+ protected $form = null;
+
+ public function __construct(&$form)
+ {
+ $this->form = $form;
+ }
+
+ public function __get($field)
+ {
+ return new IPF_Form_BoundField($this->form, $this->form->fields[$field], $field);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Model extends IPF_Form
+{
+ public $model = null;
+ public $user_fields = null;
+
+ function initFields($extra=array())
+ {
+ $this->model = $extra['model'];
+ if (isset($extra['user_fields']))
+ $this->user_fields = $extra['user_fields'];
+
+
+ $user_fields = $this->fields();
+ $db_columns = $this->model->getTable()->getColumns();
+
+
+ if ($user_fields===null){
+ foreach($db_columns as $name=>$col){
+ $this->addDBField($name,$col);
+ }
+ }
+ else{
+ foreach($user_fields as $uname){
+ if (array_key_exists($uname,$db_columns))
+ $this->addDBField($uname,$db_columns[$uname]);
+ else{
+ $add_method = 'add__'.$uname.'__field';
+ $this->$add_method();
+ }
+ }
+ }
+ }
+
+ function addDBField($name,$col){
+ if ($name==$this->model->getTable()->getIdentifier())
+ return;
+
+ $defaults = array('blank' => true, 'verbose' => $name, 'help_text' => '', 'editable' => true);
+ $type = $col['type'];
+ if (isset($col['notblank']))
+ $defaults['blank'] = false;
+ if (isset($col['length']))
+ $defaults['max_length'] = (int)($col['length']);
+ if (isset($col['email']))
+ $type = 'email';
+ $cn = 'IPF_Form_DB_'.$type;
+ $db_field = new $cn('', $name);
+
+ //echo $name;
+ //print_r($defaults);
+
+ if (null !== ($form_field=$db_field->formField($defaults))) {
+ $this->fields[$name] = $form_field;
+ }
+ }
+
+ function fields(){ return $this->user_fields; }
+
+ function save($commit=true)
+ {
+ if ($this->isValid()) {
+ $this->model->SetFromFormData($this->cleaned_data);
+
+ //print_r($this->model->data);
+
+ /*
+ if ($commit && $this->model->id) {
+ $this->model->update();
+ } elseif ($commit) {
+ $this->model->create();
+ }
+ */
+ print_r($this->model->save());
+ return $this->model;
+ }
+ throw new Exception(__('Cannot save the model from an invalid form.'));
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Widget
+{
+ public $is_hidden = false;
+ public $needs_multipart_form = false;
+ public $input_type = '';
+ public $attrs = array();
+
+ public function __construct($attrs=array())
+ {
+ $this->attrs = $attrs;
+ }
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ throw new IPF_Exception('Not Implemented.');
+ }
+
+ protected function buildAttrs($attrs, $extra_attrs=array())
+ {
+ return array_merge($this->attrs, $attrs, $extra_attrs);
+ }
+
+ public function valueFromFormData($name, $data)
+ {
+ if (isset($data[$name])) {
+ return $data[$name];
+ }
+ return null;
+ }
+
+ public function idForLabel($id)
+ {
+ return $id;
+ }
+}
+
+function IPF_Form_Widget_Attrs($attrs)
+{
+ $_tmp = array();
+ foreach ($attrs as $attr=>$val) {
+ $_tmp[] = $attr.'="'.$val.'"';
+ }
+ return ' '.implode(' ', $_tmp);
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_CheckboxInput extends IPF_Form_Widget_Input
+{
+ public $input_type = 'checkbox';
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ if ((bool)$value) {
+ $extra_attrs['checked'] = 'checked';
+ }
+ return parent::render($name, '', $extra_attrs);
+ }
+
+ public function valueFromFormData($name, $data)
+ {
+ if (!isset($data[$name])
+ or false === $data[$name] or (string)$data[$name] === '0') {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_DatetimeInput extends IPF_Form_Widget_Input
+{
+ public $input_type = 'text';
+ public $format = 'Y-m-d H:i';
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ // Internally we use GMT, so we convert back to the current
+ // timezone.
+ if (strlen($value) > 0) {
+ $value = date($this->format, strtotime($value.' GMT'));
+ }
+ return parent::render($name, $value, $extra_attrs);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_FileInput extends IPF_Form_Widget_Input
+{
+ public $input_type = 'file';
+ public $needs_multipart_form = true;
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ $value = '';
+ return parent::render($name, $value, $extra_attrs);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_HiddenInput extends IPF_Form_Widget_Input
+{
+ public $input_type = 'hidden';
+ public $is_hidden = true;
+}
--- /dev/null
+<?php
+
+class IPF_Form_Widget_HTMLInput extends IPF_Form_Widget
+{
+ public $tiny_mceurl = IPF::get('media_url').'/js/editor/tiny_mce.js';
+ public $mode = 'textareas';
+ public $theme = 'simple';
+ public $include_tinymce = true;
+
+ public function __construct($attrs=array())
+ {
+ $defaults = array('cols' => '70',
+ 'rows' => '20');
+ $config = array('tinymce_url', 'mode', 'theme', 'include_tinymce');
+ foreach ($config as $cfg) {
+ if (isset($attrs[$cfg])) {
+ $this->$cfg = $attrs[$cfg];
+ unset($attrs[$cfg]);
+ }
+ }
+ $this->attrs = array_merge($defaults, $attrs);
+ }
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ if ($value === null) $value = '';
+ $extra_config = '';
+ if (isset($this->attrs['editor_config'])) {
+ $_ec = $this->attrs['editor_config'];
+ unset($this->attrs['editor_config']);
+ $_st = array();
+ foreach ($_ec as $key=>$val) {
+ if (is_bool($val)) {
+ if ($val) {
+ $_st[] = $key.' : true';
+ } else {
+ $_st[] = $key.' : false';
+ }
+ } else {
+ $_st[] = $key.' : "'.$val.'"';
+ }
+ }
+ if ($_st) {
+ $extra_config = ",\n".implode(",\n", $_st);
+ }
+ }
+ $final_attrs = $this->buildAttrs(array('name' => $name),
+ $extra_attrs);
+ // The special include for tinyMCE
+ $out = '';
+ if ($this->include_tinymce) {
+ $out .= '<script language="javascript" type="text/javascript" src="'.$this->tinymce_url.'"></script>'."\n";
+ }
+ $out .='<script language="javascript" type="text/javascript">
+ tinyMCE.init({
+ mode : "'.$this->mode.'",
+ theme : "'.$this->theme.'"'.$extra_config.'
+ });
+</script>';
+ return new IPF_Template_SafeString(
+ $out.sprintf('<textarea%s>%s</textarea>',
+ IPF_Form_Widget_Attrs($final_attrs),
+ htmlspecialchars($value, ENT_COMPAT, 'UTF-8')),
+ true);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_Input extends IPF_Form_Widget
+{
+ public function render($name, $value, $extra_attrs=array())
+ {
+ if ($value === null) $value = '';
+ $final_attrs = $this->buildAttrs(array('name' => $name,
+ 'type' => $this->input_type),
+ $extra_attrs);
+ if ($value !== '') {
+ $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
+ $final_attrs['value'] = $value;
+ }
+ return new IPF_Template_SafeString('<input'.IPF_Form_Widget_Attrs($final_attrs).' />', true);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_PasswordInput extends IPF_Form_Widget_Input
+{
+ public $input_type = 'password';
+ public $render_value = true;
+
+ public function __construct($attrs=array())
+ {
+ $this->render_value = (isset($attrs['render_value'])) ? $attrs['render_value'] : $this->render_value;
+ unset($attrs['render_value']);
+ parent::__construct($attrs);
+ }
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ if ($this->render_value === false) {
+ $value = '';
+ }
+ return parent::render($name, $value, $extra_attrs);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_SelectInput extends IPF_Form_Widget
+{
+ public $choices = array();
+
+ public function __construct($attrs=array())
+ {
+ $this->choices = $attrs['choices'];
+ unset($attrs['choices']);
+ parent::__construct($attrs);
+ }
+
+ public function render($name, $value, $extra_attrs=array(),
+ $choices=array())
+ {
+ $output = array();
+ if ($value === null) {
+ $value = '';
+ }
+ $final_attrs = $this->buildAttrs(array('name' => $name), $extra_attrs);
+ $output[] = '<select'.IPF_Form_Widget_Attrs($final_attrs).'>';
+ $choices = $this->choices + $choices;
+ foreach ($choices as $option_label=>$option_value) {
+ $selected = ($option_value == $value) ? ' selected="selected"':'';
+ $output[] = sprintf('<option value="%s"%s>%s</option>',
+ htmlspecialchars($option_value, ENT_COMPAT, 'UTF-8'),
+ $selected,
+ htmlspecialchars($option_label, ENT_COMPAT, 'UTF-8'));
+ }
+ $output[] = '</select>';
+ return new IPF_Template_SafeString(implode("\n", $output), true);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_SelectMultipleInput extends IPF_Form_Widget
+{
+ public $choices = array();
+
+ public function __construct($attrs=array())
+ {
+ $this->choices = $attrs['choices'];
+ unset($attrs['choices']);
+ parent::__construct($attrs);
+ }
+
+ public function render($name, $value, $extra_attrs=array(),
+ $choices=array())
+ {
+ $output = array();
+ if ($value === null) {
+ $value = array();
+ }
+ $final_attrs = $this->buildAttrs(array('name' => $name.'[]'),
+ $extra_attrs);
+ $output[] = '<select multiple="multiple"'
+ .IPF_Form_Widget_Attrs($final_attrs).'>';
+ $choices = array_merge($this->choices, $choices);
+ foreach ($choices as $option_label=>$option_value) {
+ $selected = (in_array($option_value, $value)) ? ' selected="selected"':'';
+ $output[] = sprintf('<option value="%s"%s>%s</option>',
+ htmlspecialchars($option_value, ENT_COMPAT, 'UTF-8'),
+ $selected,
+ htmlspecialchars($option_label, ENT_COMPAT, 'UTF-8'));
+
+ }
+ $output[] = '</select>';
+ return new IPF_Template_SafeString(implode("\n", $output), true);
+ }
+
+ public function valueFromFormData($name, $data)
+ {
+ if (isset($data[$name]) and is_array($data[$name])) {
+ return $data[$name];
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_SelectMultipleInputCheckbox extends IPF_Form_Widget_SelectMultipleInput
+{
+ public function render($name, $value, $extra_attrs=array(),
+ $choices=array())
+ {
+ $output = array();
+ if ($value === null or $value == '') {
+ $value = array();
+ }
+ $final_attrs = $this->buildAttrs($extra_attrs);
+ $output[] = '<ul>';
+ $choices = array_merge($this->choices, $choices);
+ $i=0;
+ $base_id = $final_attrs['id'];
+ foreach ($choices as $option_label=>$option_value) {
+
+ $final_attrs['id'] = $base_id.'_'.$i;
+ $final_attrs['value'] = htmlspecialchars($option_value, ENT_COMPAT, 'UTF-8');
+ $checkbox = new IPF_Form_Widget_CheckboxInput($final_attrs);
+ $rendered = $checkbox->render($name.'[]', in_array($option_value, $value));
+
+ $output[] = sprintf('<li><label>%s %s</label></li>', $rendered,
+ htmlspecialchars($option_label, ENT_COMPAT, 'UTF-8'));
+ $i++;
+ }
+ $output[] = '</ul>';
+ return new IPF_Template_SafeString(implode("\n", $output), true);
+ }
+
+ public function idForLabel($id)
+ {
+ if ($id) {
+ $id += '_0';
+ }
+ return $id;
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Form_Widget_TextareaInput extends IPF_Form_Widget
+{
+
+ public function __construct($attrs=array())
+ {
+ $this->attrs = array_merge(array('cols' => '40', 'rows' => '10'),
+ $attrs);
+ }
+
+ public function render($name, $value, $extra_attrs=array())
+ {
+ if ($value === null) $value = '';
+ $final_attrs = $this->buildAttrs(array('name' => $name),
+ $extra_attrs);
+ return new IPF_Template_SafeString(
+ sprintf('<textarea%s>%s</textarea>',
+ IPF_Form_Widget_Attrs($final_attrs),
+ htmlspecialchars($value, ENT_COMPAT, 'UTF-8')),
+ true);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Form_Widget_TextInput extends IPF_Form_Widget_Input
+{
+ public $input_type = 'text';
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Getopt {
+
+ function getopt2($args, $short_options, $long_options = null)
+ {
+ return IPF_Getopt::doGetopt(2, $args, $short_options, $long_options);
+ }
+
+ function getopt($args, $short_options, $long_options = null)
+ {
+ return IPF_Getopt::doGetopt(1, $args, $short_options, $long_options);
+ }
+
+ function doGetopt($version, $args, $short_options, $long_options = null)
+ {
+ if (empty($args)) {
+ return array(array(), array());
+ }
+ $opts = array();
+ $non_opts = array();
+
+ settype($args, 'array');
+
+ if ($long_options) {
+ sort($long_options);
+ }
+
+ if ($version < 2) {
+ if (isset($args[0]{0}) && $args[0]{0} != '-') {
+ array_shift($args);
+ }
+ }
+
+ reset($args);
+ while (list($i, $arg) = each($args)) {
+
+ 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 = IPF_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
+ } elseif ($arg == '-') {
+ // - is stdin
+ $non_opts = array_merge($non_opts, array_slice($args, $i));
+ break;
+ } else {
+ $error = IPF_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
+ }
+ }
+
+ 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} == ':')
+ {
+ throw new IPF_Exception("IPF_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 (IPF_Getopt::_isShortOpt($opt_arg) || IPF_Getopt::_isLongOpt($opt_arg)) {
+ throw new IPF_Exception("IPF_Getopt: option requires an argument -- $opt");
+ }
+ } else {
+ throw new IPF_Exception("IPF_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} != '=') {
+ throw new IPF_Exception("IPF_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))) {
+ throw new IPF_Exception("IPF_Getopt: option --$opt requires an argument");
+ }
+ if (IPF_Getopt::_isShortOpt($opt_arg) || IPF_Getopt::_isLongOpt($opt_arg)) {
+ throw new IPF_Exception("IPF_Getopt: option requires an argument --$opt");
+ }
+ }
+ } else if ($opt_arg) {
+ throw new IPF_Exception("IPF_Getopt: option --$opt doesn't allow an argument");
+ }
+
+ $opts[] = array('--' . $opt, $opt_arg);
+ return;
+ }
+ throw new IPF_Exception("IPF_Getopt: unrecognized option --$opt");
+ }
+
+ function readPHPArgv()
+ {
+ global $argv;
+ if (!is_array($argv)) {
+ if (!@is_array($_SERVER['argv'])) {
+ if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
+ throw new IPF_Exception("IPF_Getopt: Could not read cmd args (register_argc_argv=Off?)");
+ }
+ return $GLOBALS['HTTP_SERVER_VARS']['argv'];
+ }
+ return $_SERVER['argv'];
+ }
+ return $argv;
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+class IPF_HTTP
+{
+ function removeTheMagic()
+ {
+ if (get_magic_quotes_gpc()) {
+ if (!empty($_GET)) {
+ array_walk($_GET, 'IPF_HTTP_magicStrip');
+ }
+ if (!empty($_POST)) {
+ array_walk($_POST, 'IPF_HTTP_magicStrip');
+ }
+ if (!empty($_REQUEST)) {
+ array_walk($_REQUEST, 'IPF_HTTP_magicStrip');
+ }
+ if (!empty($_COOKIE)) {
+ array_walk($_COOKIE, 'IPF_HTTP_magicStrip');
+ }
+ }
+ if (function_exists('ini_set')) {
+ @ini_set('session.use_cookies', '1');
+ @ini_set('session.use_only_cookies', '1');
+ @ini_set('session.use_trans_sid', '0');
+ @ini_set('url_rewriter.tags', '');
+ }
+ }
+}
+
+function IPF_HTTP_magicStrip(&$k, $key)
+{
+ $k = IPF_HTTP_handleMagicQuotes($k);
+}
+
+function IPF_HTTP_handleMagicQuotes(&$value)
+{
+ if (is_array($value)) {
+ $result = array();
+ foreach ($value as $k => $v) {
+ if (is_array($v)) {
+ $result[$k] = IPF_HTTP_handleMagicQuotes($v);
+ } else {
+ $result[$k] = stripslashes($v);
+ }
+ }
+ return $result;
+ } else {
+ return stripslashes($value);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_HTTP_Request
+{
+ public $POST = array();
+ public $GET = array();
+ public $REQUEST = array();
+ public $COOKIE = array();
+ public $FILES = array();
+ public $query = '';
+ public $method = '';
+ public $uri = '';
+ public $view = '';
+ public $remote_addr = '';
+ public $http_host = '';
+ public $SERVER = array();
+
+ function __construct($query)
+ {
+ $http = new IPF_HTTP();
+ $http->removeTheMagic();
+ $this->POST =& $_POST;
+ $this->GET =& $_GET;
+ $this->REQUEST =& $_REQUEST;
+ $this->COOKIE =& $_COOKIE;
+ $this->FILES =& $_FILES;
+ $this->query = $query;
+ $this->method = $_SERVER['REQUEST_METHOD'];
+ $this->uri = $_SERVER['REQUEST_URI'];
+ $this->remote_addr = $_SERVER['REMOTE_ADDR'];
+ $this->http_host = $_SERVER['HTTP_HOST'];
+ $this->SERVER =& $_SERVER;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_HTTP_Response
+{
+ public $content = '';
+ public $headers = array();
+ public $status_code = 200;
+ public $cookies = array();
+ public $status_code_list = array(
+ '100' => 'CONTINUE',
+ '101' => 'SWITCHING PROTOCOLS',
+ '200' => 'OK',
+ '201' => 'CREATED',
+ '202' => 'ACCEPTED',
+ '203' => 'NON-AUTHORITATIVE INFORMATION',
+ '204' => 'NO CONTENT',
+ '205' => 'RESET CONTENT',
+ '206' => 'PARTIAL CONTENT',
+ '300' => 'MULTIPLE CHOICES',
+ '301' => 'MOVED PERMANENTLY',
+ '302' => 'FOUND',
+ '303' => 'SEE OTHER',
+ '304' => 'NOT MODIFIED',
+ '305' => 'USE PROXY',
+ '306' => 'RESERVED',
+ '307' => 'TEMPORARY REDIRECT',
+ '400' => 'BAD REQUEST',
+ '401' => 'UNAUTHORIZED',
+ '402' => 'PAYMENT REQUIRED',
+ '403' => 'FORBIDDEN',
+ '404' => 'NOT FOUND',
+ '405' => 'METHOD NOT ALLOWED',
+ '406' => 'NOT ACCEPTABLE',
+ '407' => 'PROXY AUTHENTICATION REQUIRED',
+ '408' => 'REQUEST TIMEOUT',
+ '409' => 'CONFLICT',
+ '410' => 'GONE',
+ '411' => 'LENGTH REQUIRED',
+ '412' => 'PRECONDITION FAILED',
+ '413' => 'REQUEST ENTITY TOO LARGE',
+ '414' => 'REQUEST-URI TOO LONG',
+ '415' => 'UNSUPPORTED MEDIA TYPE',
+ '416' => 'REQUESTED RANGE NOT SATISFIABLE',
+ '417' => 'EXPECTATION FAILED',
+ '500' => 'INTERNAL SERVER ERROR',
+ '501' => 'NOT IMPLEMENTED',
+ '502' => 'BAD GATEWAY',
+ '503' => 'SERVICE UNAVAILABLE',
+ '504' => 'GATEWAY TIMEOUT',
+ '505' => 'HTTP VERSION NOT SUPPORTED'
+ );
+
+ function __construct($content='', $mimetype=null)
+ {
+ if (is_null($mimetype)) {
+ $mimetype = IPF::get('mimetype', 'text/html').'; charset=utf-8';
+ }
+ $this->content = $content;
+ $this->headers['Content-Type'] = $mimetype;
+ $this->headers['X-Powered-By'] = 'IPF - http://ipf.icmconsulting.com/';
+ $this->status_code = 200;
+ $this->cookies = array();
+ }
+
+ function render($output_body=true)
+ {
+ if ($this->status_code >= 200
+ && $this->status_code != 204
+ && $this->status_code != 304) {
+ $this->headers['Content-Length'] = strlen($this->content);
+ }
+ $this->outputHeaders();
+ if ($output_body) {
+ echo($this->content);
+ }
+ }
+
+ function outputHeaders()
+ {
+ if (!defined('IN_UNIT_TESTS')) {
+ header('HTTP/1.1 '.$this->status_code.' '
+ .$this->status_code_list[$this->status_code],
+ true, $this->status_code);
+ foreach ($this->headers as $header => $ch) {
+ header($header.': '.$ch);
+ }
+ foreach ($this->cookies as $cookie => $data) {
+ // name, data, expiration, path, domain, secure, http only
+ setcookie($cookie, $data,
+ time()+31536000,
+ IPF::get('cookie_path', '/'),
+ IPF::get('cookie_domain', null),
+ IPF::get('cookie_secure', false),
+ IPF::get('cookie_httponly', true));
+ }
+ } else {
+ $_COOKIE = array();
+ foreach ($this->cookies as $cookie => $data) {
+ $_COOKIE[$cookie] = $data;
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+
+class IPF_HTTP_Response_NotFound extends IPF_HTTP_Response
+{
+ function __construct($content='Not Found', $mimetype=null)
+ {
+ parent::__construct($content, $mimetype);
+ $this->status_code = 404;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_HTTP_Response_Redirect extends IPF_HTTP_Response
+{
+ function __construct($url)
+ {
+ $content = sprintf(__('<a href="%s">Please, click here to be redirected</a>.'), $url);
+ parent::__construct($content);
+ $this->headers['Location'] = $url;
+ $this->status_code = 302;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_HTTP_Response_ServerError extends IPF_HTTP_Response
+{
+ function __construct($content='Server Error', $mimetype=null)
+ {
+ parent::__construct($content, $mimetype);
+ $this->status_code = 500;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_HTTP_Response_ServerErrorDebug extends IPF_HTTP_Response
+{
+ function __construct($e, $mimetype=null)
+ {
+ $this->status_code = 500;
+ $this->content = IPF_HTTP_Response_ServerErrorDebug_Pretty($e);
+ }
+}
+
+function IPF_HTTP_Response_ServerErrorDebug_Pretty($e)
+{
+ $o = create_function('$in','return htmlspecialchars($in);');
+ $sub = create_function('$f','$loc="";if(isset($f["class"])){
+ $loc.=$f["class"].$f["type"];}
+ if(isset($f["function"])){$loc.=$f["function"];}
+ if(!empty($loc)){$loc=htmlspecialchars($loc);
+ $loc="<strong>$loc</strong>";}return $loc;');
+ $parms = create_function('$f','$params=array();if(isset($f["function"])){
+ try{if(isset($f["class"])){
+ $r=new ReflectionMethod($f["class"]."::".$f["function"]);}
+ else{$r=new ReflectionFunction($f["function"]);}
+ return $r->getParameters();}catch(Exception $e){}}
+ return $params;');
+ $src2lines = create_function('$file','$src=nl2br(highlight_file($file,TRUE));
+ return explode("<br />",$src);');
+ $clean = create_function('$line','return trim(strip_tags($line));');
+ $desc = get_class($e)." making ".$_SERVER['REQUEST_METHOD']." request to ".
+ $_SERVER['REQUEST_URI'];
+ $out = '
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <title>'.$o($desc).'</title>
+ <style type="text/css">
+ html * { padding:0; margin:0; }
+ body * { padding:10px 20px; }
+ body * * { padding:0; }
+ body { font:small sans-serif; background: #70DBFF; }
+ body>div { border-bottom:1px solid #ddd; }
+ h1 { font-weight:normal; }
+ h2 { margin-bottom:.8em; }
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
+ h2 a { text-decoration:none; }
+ h3 { margin:1em 0 .5em 0; }
+ h4 { margin:0.5em 0 .5em 0; font-weight: normal; font-style: italic; }
+ table {
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
+ thead th {
+ padding:1px 6px 1px 3px; background:#70FF94; text-align:left;
+ font-weight:bold; font-size:11px; border:1px solid #ddd; }
+ tbody th { text-align:right; color:#666; padding-right:.5em; }
+ table.vars { margin:5px 0 2px 40px; }
+ table.vars td, table.req td { font-family:monospace; }
+ table td { background: #70FFDB; }
+ table td.code { width:95%;}
+ table td.code div { overflow:hidden; }
+ table.source th { color:#666; }
+ table.source td {
+ font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
+ ul.traceback { list-style-type:none; }
+ ul.traceback li.frame { margin-bottom:1em; }
+ div.context { margin:5px 0 2px 40px; background-color:#70FFDB; }
+ div.context ol {
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
+ div.context ol li {
+ font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
+ div.context li.current-line { color:black; background-color:#70FF94; }
+ div.commands { margin-left: 40px; }
+ div.commands a { color:black; text-decoration:none; }
+ p.headers { background: #70FFDB; font-family:monospace; }
+ #summary { background: #00B8F5; }
+ #summary h2 { font-weight: normal; color: #666; }
+ #traceback { background:#eee; }
+ #request { background:#f6f6f6; }
+ #response { background:#eee; }
+ #summary table { border:none; background:#00B8F5; }
+ #summary td { background:#00B8F5; }
+ .switch { text-decoration: none; }
+ .whitemsg { background:white; color:black;}
+ </style>
+ <script type="text/javascript">
+ //<!--
+ function getElementsByClassName(oElm, strTagName, strClassName){
+ // Written by Jonathan Snook, http://www.snook.ca/jon;
+ // Add-ons by Robert Nyman, http://www.robertnyman.com
+ var arrElements = (strTagName == "*" && document.all)? document.all :
+ oElm.getElementsByTagName(strTagName);
+ var arrReturnElements = new Array();
+ strClassName = strClassName.replace(/\-/g, "\\-");
+ var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
+ var oElement;
+ for(var i=0; i<arrElements.length; i++){
+ oElement = arrElements[i];
+ if(oRegExp.test(oElement.className)){
+ arrReturnElements.push(oElement);
+ }
+ }
+ return (arrReturnElements)
+ }
+ function hideAll(elems) {
+ for (var e = 0; e < elems.length; e++) {
+ elems[e].style.display = \'none\';
+ }
+ }
+ function toggle() {
+ for (var i = 0; i < arguments.length; i++) {
+ var e = document.getElementById(arguments[i]);
+ if (e) {
+ e.style.display = e.style.display == \'none\' ? \'block\' : \'none\';
+ }
+ }
+ return false;
+ }
+ function varToggle(link, id, prefix) {
+ toggle(prefix + id);
+ var s = link.getElementsByTagName(\'span\')[0];
+ var uarr = String.fromCharCode(0x25b6);
+ var darr = String.fromCharCode(0x25bc);
+ s.innerHTML = s.innerHTML == uarr ? darr : uarr;
+ return false;
+ }
+ function sectionToggle(span, section) {
+ toggle(section);
+ var span = document.getElementById(span);
+ var uarr = String.fromCharCode(0x25b6);
+ var darr = String.fromCharCode(0x25bc);
+ span.innerHTML = span.innerHTML == uarr ? darr : uarr;
+ return false;
+ }
+
+ window.onload = function() {
+ hideAll(getElementsByClassName(document, \'table\', \'vars\'));
+ hideAll(getElementsByClassName(document, \'div\', \'context\'));
+ hideAll(getElementsByClassName(document, \'ul\', \'traceback\'));
+ hideAll(getElementsByClassName(document, \'div\', \'section\'));
+ }
+ //-->
+ </script>
+</head>
+<body>
+
+<div id="summary">
+ <h1>'.$o($desc).'</h1>
+ <h2>';
+ if ($e->getCode()) {
+ $out .= $o($e->getCode()). ' : ';
+ }
+ $out .= ' '.$o($e->getMessage()).'</h2>
+ <table>
+ <tr>
+ <th>PHP</th>
+ <td>'.$o($e->getFile()).', line '.$o($e->getLine()).'</td>
+ </tr>
+ <tr>
+ <th>URI</th>
+ <td>'.$o($_SERVER['REQUEST_METHOD'].' '.
+ $_SERVER['REQUEST_URI']).'</td>
+ </tr>
+ </table>
+</div>
+
+<div id="traceback">
+ <h2>Stacktrace
+ <a href=\'#\' onclick="return sectionToggle(\'tb_switch\',\'tb_list\')">
+ <span id="tb_switch">â–¶</span></a></h2>
+ <ul id="tb_list" class="traceback">';
+ $frames = $e->getTrace();
+ foreach ($frames as $frame_id=>$frame) {
+ if (!isset($frame['file'])) {
+ $frame['file'] = 'No File';
+ $frame['line'] = '0';
+ }
+ $out .= '<li class="frame">'.$sub($frame).'
+ ['.$o($frame['file']).', line '.$o($frame['line']).']';
+ if (isset($frame['args']) && count($frame['args']) > 0) {
+ $params = $parms($frame);
+ $out .= '
+ <div class="commands">
+ <a href=\'#\' onclick="return varToggle(this, \''.
+ $o($frame_id).'\',\'v\')"><span>â–¶</span> Args</a>
+ </div>
+ <table class="vars" id="v'.$o($frame_id).'">
+ <thead>
+ <tr>
+ <th>Arg</th>
+ <th>Name</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>';
+ foreach ($frame['args'] as $k => $v) {
+ $name = isset($params[$k]) ? '$'.$params[$k]->name : '?';
+ $out .= '
+ <tr>
+ <td>'.$o($k).'</td>
+ <td>'.$o($name).'</td>
+ <td class="code">
+ <div>'.highlight_string(print_r($v,true), true).'</div>
+ </td>
+ </tr>';
+ }
+ $out .= '</tbody></table>';
+ }
+ if (is_readable($frame['file']) ) {
+ $out .= '
+ <div class="commands">
+ <a href=\'#\' onclick="return varToggle(this, \''
+ .$o($frame_id).'\',\'c\')"><span>â–¶</span> Src</a>
+ </div>
+ <div class="context" id="c'.$o($frame_id).'">';
+ $lines = $src2lines($frame['file']);
+ $start = $frame['line'] < 5 ?
+ 0 : $frame['line'] -5; $end = $start + 10;
+ $out2 = '';
+ foreach ( $lines as $k => $line ) {
+ if ( $k > $end ) { break; }
+ $line = trim(strip_tags($line));
+ if ( $k < $start && isset($frames[$frame_id+1]["function"])
+ && preg_match('/function( )*'.preg_quote($frames[$frame_id+1]["function"]).'/',
+ $line) ) {
+ $start = $k;
+ }
+ if ( $k >= $start ) {
+ if ( $k != $frame['line'] ) {
+ $out2 .= '<li><code>'.$clean($line).'</code></li>'."\n"; }
+ else {
+ $out2 .= '<li class="current-line"><code>'.
+ $clean($line).'</code></li>'."\n"; }
+ }
+ }
+ $out .= "<ol start=\"$start\">\n".$out2. "</ol>\n";
+ $out .= '</div>';
+ } else {
+ $out .= '<div class="commands">No src available</div>';
+ }
+ $out .= '</li>';
+ }
+ $out .= '
+ </ul>
+
+</div>
+
+<div id="request">
+ <h2>Request
+ <a href=\'#\' onclick="return sectionToggle(\'req_switch\',\'req_list\')">
+ <span id="req_switch">â–¶</span></a></h2>
+ <div id="req_list" class="section">';
+ if ( function_exists('apache_request_headers') ) {
+ $out .= '<h3>Request <span>(raw)</span></h3>';
+ $req_headers = apache_request_headers();
+ $out .= '<h4>HEADERS</h4>';
+ if ( count($req_headers) > 0 ) {
+ $out .= '<p class="headers">';
+ foreach ($req_headers as $req_h_name => $req_h_val) {
+ $out .= $o($req_h_name.': '.$req_h_val);
+ $out .= '<br>';
+ }
+ $out .= '</p>';
+ } else {
+ $out .= '<p>No headers.</p>';
+ }
+ $req_body = file_get_contents('php://input');
+ if ( strlen( $req_body ) > 0 ) {
+ $out .='
+ <h4>Body</h4>
+ <p class="req" style="padding-bottom: 2em"><code>
+ '.$o($req_body).'
+ </code></p>';
+ }
+ }
+ $out .= '
+ <h3>Request <span>(parsed)</span></h3>';
+ $superglobals = array('$_GET','$_POST','$_COOKIE','$_SERVER','$_ENV');
+ foreach ( $superglobals as $sglobal ) {
+ $sfn = create_function('','return '.$sglobal.';');
+ $out .= '<h4>'.$sglobal.'</h4>';
+ if ( count($sfn()) > 0 ) {
+ $out .= '
+ <table class="req">
+ <thead>
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>';
+ foreach ( $sfn() as $k => $v ) {
+ $out .= '<tr>
+ <td>'.$o($k).'</td>
+ <td class="code">
+ <div>'.$o(print_r($v,TRUE)).'</div>
+ </td>
+ </tr>';
+ }
+ $out .= '
+ </tbody>
+ </table>';
+ } else {
+ $out .= '
+ <p class="whitemsg">No data</p>';
+ }
+ }
+ $out .= '
+
+ </div>
+</div>';
+ if ( function_exists('headers_list') ) {
+ $out .= '
+<div id="response">
+
+ <h2>Response
+ <a href=\'#\' onclick="return sectionToggle(\'resp_switch\',\'resp_list\')">
+ <span id="resp_switch">â–¶</span></a></h2>
+
+ <div id="resp_list" class="section">
+
+ <h3>Headers</h3>';
+ $resp_headers = headers_list();
+ if (count($resp_headers) > 0) {
+ $out .= '
+ <p class="headers">';
+ foreach ( $resp_headers as $resp_h ) {
+ $out .= $o($resp_h);
+ $out .= '<br>';
+ }
+ $out .= ' </p>';
+ } else {
+ $out .= '
+ <p>No headers.</p>';
+ }
+ $out .= '
+</div>';
+ }
+ $out .= '
+</body>
+</html>
+';
+ return $out;
+}
+
--- /dev/null
+<?php
+
+class IPF_HTTP_URL
+{
+ public static function generate($action, $params=array(), $encode=true)
+ {
+ if ($encode) {
+ $amp = '&';
+ } else {
+ $amp = '&';
+ }
+ $url = $action;
+ if (count($params) > 0) {
+ $url .= '?';
+ $params_list = array();
+ foreach ($params as $key=>$value) {
+ $params_list[] = urlencode($key).'='.urlencode($value);
+ }
+ $url .= implode($amp, $params_list);
+ }
+ return $url;
+ }
+
+ public static function getAction()
+ {
+ if (isset($_SERVER['ORIG_PATH_INFO'])) {
+ return $_SERVER['ORIG_PATH_INFO'];
+ }
+ if (isset($_SERVER['PATH_INFO'])) {
+ return $_SERVER['PATH_INFO'];
+ }
+ return '/';
+ }
+}
+
+function IPF_HTTP_URL_urlForView($view, $params=array(), $by_name=false,
+ $get_params=array(), $encoded=true)
+{
+ $action = IPF_HTTP_URL_reverse($view, $params, $by_name);
+ return IPF_HTTP_URL::generate($action, $get_params, $encoded);
+}
+
+function IPF_HTTP_URL_reverse($view, $params=array(), $by_name=false)
+{
+ $regex = null;
+
+ foreach (IPF::get('urls') as $url) {
+ $prefix = $url['prefix'];
+ foreach ($url['urls'] as $suburl){
+ if ($suburl['func']==$view){
+ $regex = $prefix.$suburl['regex'];
+ break;
+ }
+ }
+ if ($regex!==null)
+ break;
+ }
+ if ($regex === null) {
+ throw new IPF_Exception('Error, the view: '.$view.' has not been found.');
+ }
+ $url = IPF_HTTP_URL_buildReverseUrl($regex, $params);
+ return IPF::get('app_base').$url;
+}
+
+function IPF_HTTP_URL_buildReverseUrl($url_regex, $params=array())
+{
+ $url_regex = str_replace('\\.', '.', $url_regex);
+ $url_regex = str_replace('\\-', '-', $url_regex);
+ $url = $url_regex;
+ $groups = '#\(([^)]+)\)#';
+ $matches = array();
+ preg_match_all($groups, $url_regex, $matches);
+ reset($params);
+ if (count($matches[0]) && count($matches[0]) == count($params)) {
+ // Test the params against the pattern
+ foreach ($matches[0] as $pattern) {
+ $in = current($params);
+ if (0 === preg_match('#'.$pattern.'#', $in)) {
+ throw new IPF_Exception('Error, param: '.$in.' is not matching the pattern: '.$pattern);
+ }
+ next($params);
+ }
+ $func = create_function('$matches',
+ 'static $p = '.var_export($params, true).'; '.
+ '$a = current($p); '.
+ 'next($p); '.
+ 'return $a;');
+ $url = preg_replace_callback($groups, $func, $url_regex);
+ }
+ $url = substr(substr($url, 2), 0, -2);
+ if (substr($url, -1) !== '$') {
+ return $url;
+ }
+ return substr($url, 0, -1);
+}
--- /dev/null
+<?php
+
+final class IPF_ORM {
+ const ERR = -1;
+ const ERR_SYNTAX = -2;
+ const ERR_CONSTRAINT = -3;
+ const ERR_NOT_FOUND = -4;
+ const ERR_ALREADY_EXISTS = -5;
+ const ERR_UNSUPPORTED = -6;
+ const ERR_MISMATCH = -7;
+ const ERR_INVALID = -8;
+ const ERR_NOT_CAPABLE = -9;
+ const ERR_TRUNCATED = -10;
+ const ERR_INVALID_NUMBER = -11;
+ const ERR_INVALID_DATE = -12;
+ const ERR_DIVZERO = -13;
+ const ERR_NODBSELECTED = -14;
+ const ERR_CANNOT_CREATE = -15;
+ const ERR_CANNOT_DELETE = -16;
+ const ERR_CANNOT_DROP = -17;
+ const ERR_NOSUCHTABLE = -18;
+ const ERR_NOSUCHFIELD = -19;
+ const ERR_NEED_MORE_DATA = -20;
+ const ERR_NOT_LOCKED = -21;
+ const ERR_VALUE_COUNT_ON_ROW = -22;
+ const ERR_INVALID_DSN = -23;
+ const ERR_CONNECT_FAILED = -24;
+ const ERR_EXTENSION_NOT_FOUND = -25;
+ const ERR_NOSUCHDB = -26;
+ const ERR_ACCESS_VIOLATION = -27;
+ const ERR_CANNOT_REPLACE = -28;
+ const ERR_CONSTRAINT_NOT_NULL = -29;
+ const ERR_DEADLOCK = -30;
+ const ERR_CANNOT_ALTER = -31;
+ const ERR_MANAGER = -32;
+ const ERR_MANAGER_PARSE = -33;
+ const ERR_LOADMODULE = -34;
+ const ERR_INSUFFICIENT_DATA = -35;
+ const ERR_CLASS_NAME = -36;
+
+ const CASE_LOWER = 2;
+ const CASE_NATURAL = 0;
+ const CASE_UPPER = 1;
+ const CURSOR_FWDONLY = 0;
+ const CURSOR_SCROLL = 1;
+ const ERRMODE_EXCEPTION = 2;
+ const ERRMODE_SILENT = 0;
+ const ERRMODE_WARNING = 1;
+ const FETCH_ASSOC = 2;
+ const FETCH_BOTH = 4;
+ const FETCH_BOUND = 6;
+ const FETCH_CLASS = 8;
+ const FETCH_CLASSTYPE = 262144;
+ const FETCH_COLUMN = 7;
+ const FETCH_FUNC = 10;
+ const FETCH_GROUP = 65536;
+ const FETCH_INTO = 9;
+ const FETCH_LAZY = 1;
+ const FETCH_NAMED = 11;
+ const FETCH_NUM = 3;
+ const FETCH_OBJ = 5;
+ const FETCH_ORI_ABS = 4;
+ const FETCH_ORI_FIRST = 2;
+ const FETCH_ORI_LAST = 3;
+ const FETCH_ORI_NEXT = 0;
+ const FETCH_ORI_PRIOR = 1;
+ const FETCH_ORI_REL = 5;
+ const FETCH_SERIALIZE = 524288;
+ const FETCH_UNIQUE = 196608;
+ const NULL_EMPTY_STRING = 1;
+ const NULL_NATURAL = 0;
+ const NULL_TO_STRING = NULL;
+ const PARAM_BOOL = 5;
+ const PARAM_INPUT_OUTPUT = -2147483648;
+ const PARAM_INT = 1;
+ const PARAM_LOB = 3;
+ const PARAM_NULL = 0;
+ const PARAM_STMT = 4;
+ const PARAM_STR = 2;
+
+ const ATTR_AUTOCOMMIT = 0;
+ const ATTR_PREFETCH = 1;
+ const ATTR_TIMEOUT = 2;
+ const ATTR_ERRMODE = 3;
+ const ATTR_SERVER_VERSION = 4;
+ const ATTR_CLIENT_VERSION = 5;
+ const ATTR_SERVER_INFO = 6;
+ const ATTR_CONNECTION_STATUS = 7;
+ const ATTR_CASE = 8;
+ const ATTR_CURSOR_NAME = 9;
+ const ATTR_CURSOR = 10;
+ const ATTR_ORACLE_NULLS = 11;
+ const ATTR_PERSISTENT = 12;
+ const ATTR_STATEMENT_CLASS = 13;
+ const ATTR_FETCH_TABLE_NAMES = 14;
+ const ATTR_FETCH_CATALOG_NAMES = 15;
+ const ATTR_DRIVER_NAME = 16;
+ const ATTR_STRINGIFY_FETCHES = 17;
+ const ATTR_MAX_COLUMN_LEN = 18;
+
+ const ATTR_LISTENER = 100;
+ const ATTR_QUOTE_IDENTIFIER = 101;
+ const ATTR_FIELD_CASE = 102;
+ const ATTR_IDXNAME_FORMAT = 103;
+ const ATTR_SEQNAME_FORMAT = 104;
+ const ATTR_SEQCOL_NAME = 105;
+ const ATTR_CMPNAME_FORMAT = 118;
+ const ATTR_DBNAME_FORMAT = 117;
+ const ATTR_TBLCLASS_FORMAT = 119;
+ const ATTR_TBLNAME_FORMAT = 120;
+ const ATTR_EXPORT = 140;
+ const ATTR_DECIMAL_PLACES = 141;
+
+ const ATTR_PORTABILITY = 106;
+ const ATTR_VALIDATE = 107;
+ const ATTR_COLL_KEY = 108;
+ const ATTR_QUERY_LIMIT = 109;
+ const ATTR_DEFAULT_TABLE_TYPE = 112;
+ const ATTR_DEF_TEXT_LENGTH = 113;
+ const ATTR_DEF_VARCHAR_LENGTH = 114;
+ const ATTR_DEF_TABLESPACE = 115;
+ const ATTR_EMULATE_DATABASE = 116;
+ const ATTR_USE_NATIVE_ENUM = 117;
+ const ATTR_DEFAULT_SEQUENCE = 133;
+
+ //const ATTR_FETCHMODE = 118;
+ const ATTR_NAME_PREFIX = 121;
+ const ATTR_CREATE_TABLES = 122;
+ const ATTR_COLL_LIMIT = 123;
+
+ const ATTR_CACHE = 150;
+ const ATTR_RESULT_CACHE = 150;
+ const ATTR_CACHE_LIFESPAN = 151;
+ const ATTR_RESULT_CACHE_LIFESPAN = 151;
+ const ATTR_LOAD_REFERENCES = 153;
+ const ATTR_RECORD_LISTENER = 154;
+ const ATTR_THROW_EXCEPTIONS = 155;
+ const ATTR_DEFAULT_PARAM_NAMESPACE = 156;
+ const ATTR_QUERY_CACHE = 157;
+ const ATTR_QUERY_CACHE_LIFESPAN = 158;
+ const ATTR_AUTOLOAD_TABLE_CLASSES = 160;
+ const ATTR_MODEL_LOADING = 161;
+ const ATTR_RECURSIVE_MERGE_FIXTURES = 162;
+ const ATTR_SINGULARIZE_IMPORT = 163;
+ const ATTR_USE_DQL_CALLBACKS = 164;
+
+ const LIMIT_ROWS = 1;
+ const QUERY_LIMIT_ROWS = 1;
+ const LIMIT_RECORDS = 2;
+ const QUERY_LIMIT_RECORDS = 2;
+
+ const FETCH_IMMEDIATE = 0;
+ const FETCH_BATCH = 1;
+ const FETCH_OFFSET = 3;
+ const FETCH_LAZY_OFFSET = 4;
+ const FETCH_VHOLDER = 1;
+ const FETCH_RECORD = 2;
+ const FETCH_ARRAY = 3;
+
+ const PORTABILITY_NONE = 0;
+ const PORTABILITY_FIX_CASE = 1;
+ const PORTABILITY_RTRIM = 2;
+ const PORTABILITY_DELETE_COUNT = 4;
+ const PORTABILITY_EMPTY_TO_NULL = 8;
+ const PORTABILITY_FIX_ASSOC_FIELD_NAMES = 16;
+ const PORTABILITY_EXPR = 32;
+ const PORTABILITY_ALL = 63;
+
+ const LOCK_OPTIMISTIC = 0;
+ const LOCK_PESSIMISTIC = 1;
+
+ const EXPORT_NONE = 0;
+ const EXPORT_TABLES = 1;
+ const EXPORT_CONSTRAINTS = 2;
+ const EXPORT_PLUGINS = 4;
+ const EXPORT_ALL = 7;
+ const HYDRATE_RECORD = 2;
+ const HYDRATE_ARRAY = 3;
+
+ const HYDRATE_NONE = 4;
+ const VALIDATE_NONE = 0;
+ const VALIDATE_LENGTHS = 1;
+ const VALIDATE_TYPES = 2;
+ const VALIDATE_CONSTRAINTS = 4;
+ const VALIDATE_ALL = 7;
+ const IDENTIFIER_AUTOINC = 1;
+ const IDENTIFIER_SEQUENCE = 2;
+ const IDENTIFIER_NATURAL = 3;
+ const IDENTIFIER_COMPOSITE = 4;
+ const MODEL_LOADING_AGGRESSIVE = 1;
+ const MODEL_LOADING_CONSERVATIVE= 2;
+
+ private static $_loadedModelFiles = array();
+
+ public function __construct(){
+ throw new IPF_Exception_Base('IPF_Const is static class. No instances can be created.');
+ }
+
+ public static function getTable($componentName)
+ {
+ return IPF_ORM_Manager::getInstance()->getConnectionForComponent($componentName)->getTable($componentName);
+ }
+
+ public static function generateModelsFromYaml($yamlPath, $directory, $options = array())
+ {
+ $import = new IPF_ORM_Import_Schema();
+ $import->setOptions($options);
+ $import->importSchema($yamlPath, 'yml', $directory);
+ }
+
+ public static function createTablesFromModels($directory)
+ {
+ return IPF_ORM_Manager::connection()->export->exportSchema($directory);
+ }
+
+ public static function generateSqlFromModels($directory = null)
+ {
+ $sql = IPF_ORM_Manager::connection()->export->exportSql($directory);
+ $build = '';
+ foreach ($sql as $query) {
+ $build .= $query.";\n";
+ }
+ return $build;
+ }
+
+ public static function loadModel($className, $path = null)
+ {
+ self::$_loadedModelFiles[$className] = $path;
+ }
+
+ public static function filterInvalidModels($classes)
+ {
+ $validModels = array();
+ foreach ((array) $classes as $name) {
+ if (self::isValidModelClass($name) && ! in_array($name, $validModels)) {
+ $validModels[] = $name;
+ }
+ }
+
+ return $validModels;
+ }
+
+ public static function loadModels($directory, $modelLoading = null)
+ {
+ $manager = IPF_ORM_Manager::getInstance();
+
+ $modelLoading = $modelLoading === null ? $manager->getAttribute(IPF_ORM::ATTR_MODEL_LOADING):$modelLoading;
+ $loadedModels = array();
+ if ($directory !== null) {
+ foreach ((array) $directory as $dir) {
+ if ( ! is_dir($dir)) {
+ throw new IPF_ORM_Exception('You must pass a valid path to a directory containing IPF_ORM models');
+ }
+ $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY);
+ foreach ($it as $file) {
+ $e = explode('.', $file->getFileName());
+ if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) {
+ $className = $e[0];
+ if ($modelLoading == IPF_ORM::MODEL_LOADING_CONSERVATIVE) {
+ self::loadModel($className, $file->getPathName());
+
+ $loadedModels[$className] = $className;
+ } else {
+ //$declaredBefore = get_declared_classes();
+ require_once($file->getPathName());
+ $loadedModels[$className] = $className; // !!!
+
+ /*
+ //$declaredAfter = get_declared_classes();
+ //$foundClasses = array_slice($declaredAfter, count($declaredBefore) - 1);
+ if ($foundClasses) {
+ foreach ($foundClasses as $className) {
+ if (self::isValidModelClass($className)) {
+ $loadedModels[$className] = $className;
+
+ self::loadModel($className, $file->getPathName());
+ }
+ }
+ }
+ */
+ }
+ }
+ }
+ }
+ }
+ return $loadedModels;
+ }
+
+
+ public static function isValidModelClass($class)
+ {
+ if ($class instanceof IPF_ORM_Record) {
+ $class = get_class($class);
+ }
+ if (is_string($class) && class_exists($class)) {
+ $class = new ReflectionClass($class);
+ }
+ if ($class instanceof ReflectionClass) {
+ if ( ! $class->isAbstract() && $class->isSubClassOf('IPF_ORM_Record')) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static function dump($var, $output = true, $indent = "")
+ {
+ $ret = array();
+ switch (gettype($var)) {
+ case 'array':
+ $ret[] = 'Array(';
+ $indent .= " ";
+ foreach ($var as $k => $v) {
+
+ $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent);
+ }
+ $indent = substr($indent,0, -4);
+ $ret[] = $indent . ")";
+ break;
+ case 'object':
+ $ret[] = 'Object(' . get_class($var) . ')';
+ break;
+ default:
+ $ret[] = var_export($var, true);
+ }
+
+ if ($output) {
+ print implode("\n", $ret);
+ }
+
+ return implode("\n", $ret);
+ }
+}
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Access extends IPF_ORM_Locator_Injectable implements ArrayAccess
+{
+ public function setArray(array $array)
+ {
+ foreach ($array as $k => $v) {
+ $this->set($k, $v);
+ }
+
+ return $this;
+ }
+
+ public function __set($name, $value)
+ {
+ $this->set($name, $value);
+ }
+
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ public function __isset($name)
+ {
+ return $this->contains($name);
+ }
+
+ public function __unset($name)
+ {
+ return $this->remove($name);
+ }
+
+ public function offsetExists($offset)
+ {
+ return $this->contains($offset);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if ( ! isset($offset)) {
+ $this->add($value);
+ } else {
+ $this->set($offset, $value);
+ }
+ }
+
+ public function offsetUnset($offset)
+ {
+ return $this->remove($offset);
+ }
+
+ public function remove($offset)
+ {
+ throw new IPF_ORM_Exception('Remove is not supported for ' . get_class($this));
+ }
+
+ public function get($offset)
+ {
+ throw new IPF_ORM_Exception('Get is not supported for ' . get_class($this));
+ }
+
+ public function set($offset, $value)
+ {
+ throw new IPF_ORM_Exception('Set is not supported for ' . get_class($this));
+ }
+
+ public function contains($offset)
+ {
+ throw new IPF_ORM_Exception('Contains is not supported for ' . get_class($this));
+ }
+
+ public function add($value)
+ {
+ throw new IPF_ORM_Exception('Add is not supported for ' . get_class($this));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Adapter
+{
+ const ATTR_AUTOCOMMIT = 0;
+ const ATTR_CASE = 8;
+ const ATTR_CLIENT_VERSION = 5;
+ const ATTR_CONNECTION_STATUS = 7;
+ const ATTR_CURSOR = 10;
+ const ATTR_CURSOR_NAME = 9;
+ const ATTR_DRIVER_NAME = 16;
+ const ATTR_ERRMODE = 3;
+ const ATTR_FETCH_CATALOG_NAMES = 15;
+ const ATTR_FETCH_TABLE_NAMES = 14;
+ const ATTR_MAX_COLUMN_LEN = 18;
+ const ATTR_ORACLE_NULLS = 11;
+ const ATTR_PERSISTENT = 12;
+ const ATTR_PREFETCH = 1;
+ const ATTR_SERVER_INFO = 6;
+ const ATTR_SERVER_VERSION = 4;
+ const ATTR_STATEMENT_CLASS = 13;
+ const ATTR_STRINGIFY_FETCHES = 17;
+ const ATTR_TIMEOUT = 2;
+ const CASE_LOWER = 2;
+ const CASE_NATURAL = 0;
+ const CASE_UPPER = 1;
+ const CURSOR_FWDONLY = 0;
+ const CURSOR_SCROLL = 1;
+ const ERR_ALREADY_EXISTS = NULL;
+ const ERR_CANT_MAP = NULL;
+ const ERR_CONSTRAINT = NULL;
+ const ERR_DISCONNECTED = NULL;
+ const ERR_MISMATCH = NULL;
+ const ERR_NO_PERM = NULL;
+ const ERR_NONE = '00000';
+ const ERR_NOT_FOUND = NULL;
+ const ERR_NOT_IMPLEMENTED = NULL;
+ const ERR_SYNTAX = NULL;
+ const ERR_TRUNCATED = NULL;
+ const ERRMODE_EXCEPTION = 2;
+ const ERRMODE_SILENT = 0;
+ const ERRMODE_WARNING = 1;
+ const FETCH_ASSOC = 2;
+ const FETCH_BOTH = 4;
+ const FETCH_BOUND = 6;
+ const FETCH_CLASS = 8;
+ const FETCH_CLASSTYPE = 262144;
+ const FETCH_COLUMN = 7;
+ const FETCH_FUNC = 10;
+ const FETCH_GROUP = 65536;
+ const FETCH_INTO = 9;
+ const FETCH_LAZY = 1;
+ const FETCH_NAMED = 11;
+ const FETCH_NUM = 3;
+ const FETCH_OBJ = 5;
+ const FETCH_ORI_ABS = 4;
+ const FETCH_ORI_FIRST = 2;
+ const FETCH_ORI_LAST = 3;
+ const FETCH_ORI_NEXT = 0;
+ const FETCH_ORI_PRIOR = 1;
+ const FETCH_ORI_REL = 5;
+ const FETCH_SERIALIZE = 524288;
+ const FETCH_UNIQUE = 196608;
+ const NULL_EMPTY_STRING = 1;
+ const NULL_NATURAL = 0;
+ const NULL_TO_STRING = NULL;
+ const PARAM_BOOL = 5;
+ const PARAM_INPUT_OUTPUT = -2147483648;
+ const PARAM_INT = 1;
+ const PARAM_LOB = 3;
+ const PARAM_NULL = 0;
+ const PARAM_STMT = 4;
+ const PARAM_STR = 2;
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+interface IPF_ORM_Adapter_Statement_Interface
+{
+ public function bindColumn($column, $param, $type = null);
+ public function bindValue($param, $value, $type = null);
+ public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array());
+ public function closeCursor();
+ public function columnCount();
+ public function errorCode();
+ public function errorInfo();
+ public function execute($params = null);
+ public function fetch($fetchStyle = IPF_ORM::FETCH_BOTH,
+ $cursorOrientation = IPF_ORM::FETCH_ORI_NEXT,
+ $cursorOffset = null);
+ public function fetchAll($fetchStyle = IPF_ORM::FETCH_BOTH);
+ public function fetchColumn($columnIndex = 0);
+ public function fetchObject($className = 'stdClass', $args = array());
+ public function getAttribute($attribute);
+ public function getColumnMeta($column);
+ public function nextRowset();
+ public function rowCount();
+ public function setAttribute($attribute, $value);
+ public function setFetchMode($mode, $arg1 = null, $arg2 = null);
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Builder
+{
+ public function varExport($var)
+ {
+ $export = var_export($var, true);
+ $export = str_replace("\n", '', $export);
+ $export = str_replace(' ', ' ', $export);
+ $export = str_replace('array ( ', 'array(', $export);
+ $export = str_replace('array( ', 'array(', $export);
+ $export = str_replace(',)', ')', $export);
+ $export = str_replace(', )', ')', $export);
+ $export = str_replace(' ', ' ', $export);
+
+ return $export;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+interface IPF_ORM_Cache_Interface
+{
+ public function fetch($id, $testCacheValidity = true);
+ public function contains($id);
+ public function save($id, $data, $lifeTime = false);
+ public function delete($id);
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Collection extends IPF_ORM_Access implements Countable, IteratorAggregate, Serializable
+{
+ protected $data = array();
+ protected $_table;
+ protected $_snapshot = array();
+ protected $reference;
+ protected $referenceField;
+ protected $relation;
+ protected $keyColumn;
+ protected static $null;
+
+ public function __construct($table, $keyColumn = null)
+ {
+ if ( ! ($table instanceof IPF_ORM_Table)) {
+ $table = IPF_ORM::getTable($table);
+ }
+
+ $this->_table = $table;
+
+ if ($keyColumn === null) {
+ $keyColumn = $table->getBoundQueryPart('indexBy');
+ }
+
+ if ($keyColumn === null) {
+ $keyColumn = $table->getAttribute(IPF_ORM::ATTR_COLL_KEY);
+ }
+
+ if ($keyColumn !== null) {
+ $this->keyColumn = $keyColumn;
+ }
+ }
+
+ public static function initNullObject(IPF_ORM_Null $null)
+ {
+ self::$null = $null;
+ }
+
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ public function setData(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function serialize()
+ {
+ $vars = get_object_vars($this);
+
+ unset($vars['reference']);
+ unset($vars['reference_field']);
+ unset($vars['relation']);
+ unset($vars['expandable']);
+ unset($vars['expanded']);
+ unset($vars['generator']);
+
+ $vars['_table'] = $vars['_table']->getComponentName();
+
+ return serialize($vars);
+ }
+
+ public function unserialize($serialized)
+ {
+ $manager = IPF_ORM_Manager::getInstance();
+ $connection = $manager->getCurrentConnection();
+
+ $array = unserialize($serialized);
+
+ foreach ($array as $name => $values) {
+ $this->$name = $values;
+ }
+
+ $this->_table = $connection->getTable($this->_table);
+
+ $keyColumn = isset($array['keyColumn']) ? $array['keyColumn'] : null;
+ if ($keyColumn === null) {
+ $keyColumn = $this->_table->getBoundQueryPart('indexBy');
+ }
+
+ if ($keyColumn !== null) {
+ $this->keyColumn = $keyColumn;
+ }
+ }
+
+ public function setKeyColumn($column)
+ {
+ $this->keyColumn = $column;
+
+ return $this;
+ }
+
+ public function getKeyColumn()
+ {
+ return $this->keyColumn;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getFirst()
+ {
+ return reset($this->data);
+ }
+
+ public function getLast()
+ {
+ return end($this->data);
+ }
+
+ public function end()
+ {
+ return end($this->data);
+ }
+
+ public function key()
+ {
+ return key($this->data);
+ }
+
+ public function setReference(IPF_ORM_Record $record, IPF_ORM_Relation $relation)
+ {
+ $this->reference = $record;
+ $this->relation = $relation;
+
+ if ($relation instanceof IPF_ORM_Relation_ForeignKey ||
+ $relation instanceof IPF_ORM_Relation_LocalKey) {
+
+ $this->referenceField = $relation->getForeignFieldName();
+
+ $value = $record->get($relation->getLocalFieldName());
+
+ foreach ($this->data as $record) {
+ if ($value !== null) {
+ $record->set($this->referenceField, $value, false);
+ } else {
+ $record->set($this->referenceField, $this->reference, false);
+ }
+ }
+ } elseif ($relation instanceof IPF_ORM_Relation_Association) {
+
+ }
+ }
+
+ public function getReference()
+ {
+ return $this->reference;
+ }
+
+ public function remove($key)
+ {
+ $removed = $this->data[$key];
+
+ unset($this->data[$key]);
+ return $removed;
+ }
+
+ public function contains($key)
+ {
+ return isset($this->data[$key]);
+ }
+
+ public function search(IPF_ORM_Record $record)
+ {
+ return array_search($record, $this->data, true);
+ }
+
+ public function get($key)
+ {
+ if ( ! isset($this->data[$key])) {
+ $record = $this->_table->create();
+
+ if (isset($this->referenceField)) {
+ $value = $this->reference->get($this->relation->getLocalFieldName());
+
+ if ($value !== null) {
+ $record->set($this->referenceField, $value, false);
+ } else {
+ $record->set($this->referenceField, $this->reference, false);
+ }
+ }
+ if ($key === null) {
+ $this->data[] = $record;
+ } else {
+ $this->data[$key] = $record;
+ }
+
+ if (isset($this->keyColumn)) {
+ $record->set($this->keyColumn, $key);
+ }
+
+ return $record;
+ }
+
+ return $this->data[$key];
+ }
+
+ public function getPrimaryKeys()
+ {
+ $list = array();
+ $name = $this->_table->getIdentifier();
+
+ foreach ($this->data as $record) {
+ if (is_array($record) && isset($record[$name])) {
+ $list[] = $record[$name];
+ } else {
+ $list[] = $record->getIncremented();
+ }
+ }
+ return $list;
+ }
+
+ public function getKeys()
+ {
+ return array_keys($this->data);
+ }
+
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ public function set($key, $record)
+ {
+ if (isset($this->referenceField)) {
+ $record->set($this->referenceField, $this->reference, false);
+ }
+
+ $this->data[$key] = $record;
+ }
+
+ public function add($record, $key = null)
+ {
+ if (isset($this->referenceField)) {
+ $value = $this->reference->get($this->relation->getLocalFieldName());
+
+ if ($value !== null) {
+ $record->set($this->referenceField, $value, false);
+ } else {
+ $record->set($this->referenceField, $this->reference, false);
+ }
+ }
+ /**
+ * for some weird reason in_array cannot be used here (php bug ?)
+ *
+ * if used it results in fatal error : [ nesting level too deep ]
+ */
+ foreach ($this->data as $val) {
+ if ($val === $record) {
+ return false;
+ }
+ }
+
+ if (isset($key)) {
+ if (isset($this->data[$key])) {
+ return false;
+ }
+ $this->data[$key] = $record;
+ return true;
+ }
+
+ if (isset($this->keyColumn)) {
+ $value = $record->get($this->keyColumn);
+ if ($value === null) {
+ throw new IPF_ORM_Exception("Couldn't create collection index. Record field '".$this->keyColumn."' was null.");
+ }
+ $this->data[$value] = $record;
+ } else {
+ $this->data[] = $record;
+ }
+
+ return true;
+ }
+
+ public function merge(IPF_ORM_Collection $coll)
+ {
+ $localBase = $this->getTable()->getComponentName();
+ $otherBase = $coll->getTable()->getComponentName();
+
+ if ($otherBase != $localBase && !is_subclass_of($otherBase, $localBase) ) {
+ throw new IPF_ORM_Exception("Can't merge collections with incompatible record types");
+ }
+
+ foreach ($coll->getData() as $record) {
+ $this->add($record);
+ }
+
+ return $this;
+ }
+
+ public function loadRelated($name = null)
+ {
+ $list = array();
+ $query = new IPF_ORM_Query($this->_table->getConnection());
+
+ if ( ! isset($name)) {
+ foreach ($this->data as $record) {
+ $value = $record->getIncremented();
+ if ($value !== null) {
+ $list[] = $value;
+ }
+ }
+ $query->from($this->_table->getComponentName());
+ $query->where($this->_table->getComponentName() . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')');
+
+ return $query;
+ }
+
+ $rel = $this->_table->getRelation($name);
+
+ if ($rel instanceof IPF_ORM_Relation_LocalKey || $rel instanceof IPF_ORM_Relation_ForeignKey) {
+ foreach ($this->data as $record) {
+ $list[] = $record[$rel->getLocal()];
+ }
+ } else {
+ foreach ($this->data as $record) {
+ $value = $record->getIncremented();
+ if ($value !== null) {
+ $list[] = $value;
+ }
+ }
+ }
+
+ $dql = $rel->getRelationDql(count($list), 'collection');
+
+ $coll = $query->query($dql, $list);
+
+ $this->populateRelated($name, $coll);
+ }
+
+ public function populateRelated($name, IPF_ORM_Collection $coll)
+ {
+ $rel = $this->_table->getRelation($name);
+ $table = $rel->getTable();
+ $foreign = $rel->getForeign();
+ $local = $rel->getLocal();
+
+ if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+ foreach ($this->data as $key => $record) {
+ foreach ($coll as $k => $related) {
+ if ($related[$foreign] == $record[$local]) {
+ $this->data[$key]->setRelated($name, $related);
+ }
+ }
+ }
+ } elseif ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+ foreach ($this->data as $key => $record) {
+ if ( ! $record->exists()) {
+ continue;
+ }
+ $sub = new IPF_ORM_Collection($table);
+
+ foreach ($coll as $k => $related) {
+ if ($related[$foreign] == $record[$local]) {
+ $sub->add($related);
+ $coll->remove($k);
+ }
+ }
+
+ $this->data[$key]->setRelated($name, $sub);
+ }
+ } elseif ($rel instanceof IPF_ORM_Relation_Association) {
+ $identifier = $this->_table->getIdentifier();
+ $asf = $rel->getAssociationFactory();
+ $name = $table->getComponentName();
+
+ foreach ($this->data as $key => $record) {
+ if ( ! $record->exists()) {
+ continue;
+ }
+ $sub = new IPF_ORM_Collection($table);
+ foreach ($coll as $k => $related) {
+ if ($related->get($local) == $record[$identifier]) {
+ $sub->add($related->get($name));
+ }
+ }
+ $this->data[$key]->setRelated($name, $sub);
+
+ }
+ }
+ }
+
+ public function getNormalIterator()
+ {
+ return new IPF_ORM_Collection_Iterator_Normal($this);
+ }
+
+ public function takeSnapshot()
+ {
+ $this->_snapshot = $this->data;
+
+ return $this;
+ }
+
+ public function getSnapshot()
+ {
+ return $this->_snapshot;
+ }
+
+ public function processDiff()
+ {
+ foreach (array_udiff($this->_snapshot, $this->data, array($this, "compareRecords")) as $record) {
+ $record->delete();
+ }
+
+ return $this;
+ }
+
+ public function toArray($deep = false, $prefixKey = false)
+ {
+ $data = array();
+ foreach ($this as $key => $record) {
+
+ $key = $prefixKey ? get_class($record) . '_' .$key:$key;
+
+ $data[$key] = $record->toArray($deep, $prefixKey);
+ }
+
+ return $data;
+ }
+
+ public function fromArray($array, $deep = true)
+ {
+ $data = array();
+ foreach ($array as $rowKey => $row) {
+ $this[$rowKey]->fromArray($row, $deep);
+ }
+ }
+
+ public function synchronizeWithArray(array $array)
+ {
+ foreach ($this as $key => $record) {
+ if (isset($array[$key])) {
+ $record->synchronizeWithArray($array[$key]);
+ unset($array[$key]);
+ } else {
+ // remove records that don't exist in the array
+ $this->remove($key);
+ }
+ }
+ // create new records for each new row in the array
+ foreach ($array as $rowKey => $row) {
+ $this[$rowKey]->fromArray($row);
+ }
+ }
+ public function synchronizeFromArray(array $array)
+ {
+ return $this->synchronizeWithArray($array);
+ }
+
+ public function exportTo($type, $deep = false)
+ {
+ if ($type == 'array') {
+ return $this->toArray($deep);
+ } else {
+ return IPF_ORM_Parser::dump($this->toArray($deep, true), $type);
+ }
+ }
+
+ public function importFrom($type, $data)
+ {
+ if ($type == 'array') {
+ return $this->fromArray($data);
+ } else {
+ return $this->fromArray(IPF_ORM_Parser::load($data, $type));
+ }
+ }
+
+ public function getDeleteDiff()
+ {
+ return array_udiff($this->_snapshot, $this->data, array($this, 'compareRecords'));
+ }
+
+ public function getInsertDiff()
+ {
+ return array_udiff($this->data, $this->_snapshot, array($this, "compareRecords"));
+ }
+
+ protected function compareRecords($a, $b)
+ {
+ if ($a->getOid() == $b->getOid()) {
+ return 0;
+ }
+
+ return ($a->getOid() > $b->getOid()) ? 1 : -1;
+ }
+
+ public function save(IPF_ORM_Connection $conn = null)
+ {
+ if ($conn == null) {
+ $conn = $this->_table->getConnection();
+ }
+
+ $conn->beginInternalTransaction();
+
+ $conn->transaction->addCollection($this);
+
+ $this->processDiff();
+
+ foreach ($this->getData() as $key => $record) {
+ $record->save($conn);
+ }
+
+ $conn->commit();
+
+ return $this;
+ }
+
+ public function delete(IPF_ORM_Connection $conn = null, $clearColl = true)
+ {
+ if ($conn == null) {
+ $conn = $this->_table->getConnection();
+ }
+
+ $conn->beginInternalTransaction();
+ $conn->transaction->addCollection($this);
+
+ foreach ($this as $key => $record) {
+ $record->delete($conn);
+ }
+
+ $conn->commit();
+
+ if ($clearColl) {
+ $this->clear();
+ }
+
+ return $this;
+ }
+
+ public function clear()
+ {
+ $this->data = array();
+ }
+
+ public function free($deep = false)
+ {
+ foreach ($this->getData() as $key => $record) {
+ if ( ! ($record instanceof IPF_ORM_Null)) {
+ $record->free($deep);
+ }
+ }
+
+ $this->data = array();
+
+ if ($this->reference) {
+ $this->reference->free($deep);
+ $this->reference = null;
+ }
+ }
+
+ public function getIterator()
+ {
+ $data = $this->data;
+ return new ArrayIterator($data);
+ }
+
+ public function __toString()
+ {
+ return IPF_ORM_Utils::getCollectionAsString($this);
+ }
+
+ public function getRelation()
+ {
+ return $this->relation;
+ }
+}
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Configurable extends IPF_ORM_Locator_Injectable
+{
+ protected $attributes = array();
+ protected $parent;
+ protected $_impl = array();
+ protected $_params = array();
+
+ public function getAttributeFromString($stringAttributeName)
+ {
+ if (is_string($stringAttributeName)) {
+ $upper = strtoupper($stringAttributeName);
+ $const = 'IPF_ORM::ATTR_' . $upper;
+ if (defined($const)) {
+ return constant($const);
+ } else {
+ throw new IPF_ORM_Exception('Unknown attribute: "' . $stringAttributeName . '"');
+ }
+ } else {
+ return false;
+ }
+ }
+
+ public function getAttributeValueFromString($stringAttributeName, $stringAttributeValueName)
+ {
+ $const = 'IPF_ORM::' . strtoupper($stringAttributeName) . '_' . strtoupper($stringAttributeValueName);
+ if (defined($const)) {
+ return constant($const);
+ } else {
+ throw new IPF_ORM_Exception('Unknown attribute value: "' . $value . '"');
+ }
+ }
+
+ public function setAttribute($attribute, $value)
+ {
+ if (is_string($attribute)) {
+ $stringAttribute = $attribute;
+ $attribute = $this->getAttributeFromString($attribute);
+ $this->_state = $attribute;
+ }
+
+ if (is_string($value) && isset($stringAttribute)) {
+ $value = $this->getAttributeValueFromString($stringAttribute, $value);
+ }
+
+ switch ($attribute) {
+ case IPF_ORM::ATTR_LISTENER:
+ $this->setEventListener($value);
+ break;
+ case IPF_ORM::ATTR_COLL_KEY:
+ if ( ! ($this instanceof IPF_ORM_Table)) {
+ throw new IPF_ORM_Exception("This attribute can only be set at table level.");
+ }
+ if ($value !== null && ! $this->hasField($value)) {
+ throw new IPF_ORM_Exception("Couldn't set collection key attribute. No such field '$value'.");
+ }
+ break;
+ case IPF_ORM::ATTR_CACHE:
+ case IPF_ORM::ATTR_RESULT_CACHE:
+ case IPF_ORM::ATTR_QUERY_CACHE:
+ if ($value !== null) {
+ if ( ! ($value instanceof IPF_ORM_Cache_Interface)) {
+ throw new IPF_ORM_Exception('Cache driver should implement IPF_ORM_Cache_Interface');
+ }
+ }
+ break;
+ case IPF_ORM::ATTR_VALIDATE:
+ case IPF_ORM::ATTR_QUERY_LIMIT:
+ case IPF_ORM::ATTR_QUOTE_IDENTIFIER:
+ case IPF_ORM::ATTR_PORTABILITY:
+ case IPF_ORM::ATTR_DEFAULT_TABLE_TYPE:
+ case IPF_ORM::ATTR_EMULATE_DATABASE:
+ case IPF_ORM::ATTR_USE_NATIVE_ENUM:
+ case IPF_ORM::ATTR_DEFAULT_SEQUENCE:
+ case IPF_ORM::ATTR_EXPORT:
+ case IPF_ORM::ATTR_DECIMAL_PLACES:
+ case IPF_ORM::ATTR_LOAD_REFERENCES:
+ case IPF_ORM::ATTR_RECORD_LISTENER:
+ case IPF_ORM::ATTR_THROW_EXCEPTIONS:
+ case IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE:
+ case IPF_ORM::ATTR_AUTOLOAD_TABLE_CLASSES:
+ case IPF_ORM::ATTR_MODEL_LOADING:
+ case IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN:
+ case IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN:
+ case IPF_ORM::ATTR_RECURSIVE_MERGE_FIXTURES;
+ case IPF_ORM::ATTR_SINGULARIZE_IMPORT;
+ case IPF_ORM::ATTR_USE_DQL_CALLBACKS;
+
+ break;
+ case IPF_ORM::ATTR_SEQCOL_NAME:
+ if ( ! is_string($value)) {
+ throw new IPF_ORM_Exception('Sequence column name attribute only accepts string values');
+ }
+ break;
+ case IPF_ORM::ATTR_FIELD_CASE:
+ if ($value != 0 && $value != CASE_LOWER && $value != CASE_UPPER)
+ throw new IPF_ORM_Exception('Field case attribute should be either 0, CASE_LOWER or CASE_UPPER constant.');
+ break;
+ case IPF_ORM::ATTR_SEQNAME_FORMAT:
+ case IPF_ORM::ATTR_IDXNAME_FORMAT:
+ case IPF_ORM::ATTR_TBLNAME_FORMAT:
+ if ($this instanceof IPF_ORM_Table) {
+ throw new IPF_ORM_Exception('Sequence / index name format attributes cannot be set'
+ . 'at table level (only at connection or global level).');
+ }
+ break;
+ default:
+ throw new IPF_ORM_Exception("Unknown attribute.");
+ }
+
+ $this->attributes[$attribute] = $value;
+ }
+
+ public function getParams($namespace = null)
+ {
+ if ($namespace == null) {
+ $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
+ }
+
+ if ( ! isset($this->_params[$namespace])) {
+ return null;
+ }
+
+ return $this->_params[$namespace];
+ }
+
+ public function getParamNamespaces()
+ {
+ return array_keys($this->_params);
+ }
+
+ public function setParam($name, $value, $namespace = null)
+ {
+ if ($namespace == null) {
+ $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
+ }
+
+ $this->_params[$namespace][$name] = $value;
+
+ return $this;
+ }
+
+ public function getParam($name, $namespace = null)
+ {
+ if ($namespace == null) {
+ $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
+ }
+
+ if ( ! isset($this->_params[$name])) {
+ if (isset($this->parent)) {
+ return $this->parent->getParam($name, $namespace);
+ }
+ return null;
+ }
+
+ return $this->_params[$namespace][$name];
+ }
+
+ public function setImpl($template, $class)
+ {
+ $this->_impl[$template] = $class;
+
+ return $this;
+ }
+
+ public function getImpl($template)
+ {
+ if ( ! isset($this->_impl[$template])) {
+ if (isset($this->parent)) {
+ return $this->parent->getImpl($template);
+ }
+ return null;
+ }
+ return $this->_impl[$template];
+ }
+
+
+ public function hasImpl($template)
+ {
+ if ( ! isset($this->_impl[$template])) {
+ if (isset($this->parent)) {
+ return $this->parent->hasImpl($template);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public function setEventListener($listener)
+ {
+ return $this->setListener($listener);
+ }
+
+ public function addRecordListener($listener, $name = null)
+ {
+ if ( ! isset($this->attributes[IPF_ORM::ATTR_RECORD_LISTENER]) ||
+ ! ($this->attributes[IPF_ORM::ATTR_RECORD_LISTENER] instanceof IPF_ORM_Record_Listener_Chain)) {
+
+ $this->attributes[IPF_ORM::ATTR_RECORD_LISTENER] = new IPF_ORM_Record_Listener_Chain();
+ }
+ $this->attributes[IPF_ORM::ATTR_RECORD_LISTENER]->add($listener, $name);
+
+ return $this;
+ }
+
+ public function getRecordListener()
+ {
+ if ( ! isset($this->attributes[IPF_ORM::ATTR_RECORD_LISTENER])) {
+ if (isset($this->parent)) {
+ return $this->parent->getRecordListener();
+ }
+ return null;
+ }
+ return $this->attributes[IPF_ORM::ATTR_RECORD_LISTENER];
+ }
+
+ public function setRecordListener($listener)
+ {
+ if ( ! ($listener instanceof IPF_ORM_Record_Listener_Interface)
+ && ! ($listener instanceof IPF_ORM_Overloadable)
+ ) {
+ throw new IPF_ORM_Exception("Couldn't set eventlistener. Record listeners should implement either IPF_ORM_Record_Listener_Interface or IPF_ORM_Overloadable");
+ }
+ $this->attributes[IPF_ORM::ATTR_RECORD_LISTENER] = $listener;
+
+ return $this;
+ }
+
+ public function addListener($listener, $name = null)
+ {
+ if ( ! isset($this->attributes[IPF_ORM::ATTR_LISTENER]) ||
+ ! ($this->attributes[IPF_ORM::ATTR_LISTENER] instanceof IPF_ORM_EventListener_Chain)) {
+
+ $this->attributes[IPF_ORM::ATTR_LISTENER] = new IPF_ORM_EventListener_Chain();
+ }
+ $this->attributes[IPF_ORM::ATTR_LISTENER]->add($listener, $name);
+
+ return $this;
+ }
+
+ public function getListener()
+ {
+ if ( ! isset($this->attributes[IPF_ORM::ATTR_LISTENER])) {
+ if (isset($this->parent)) {
+ return $this->parent->getListener();
+ }
+ return null;
+ }
+ return $this->attributes[IPF_ORM::ATTR_LISTENER];
+ }
+
+ public function setListener($listener)
+ {
+ if ( ! ($listener instanceof IPF_ORM_EventListener_Interface)
+ && ! ($listener instanceof IPF_ORM_Overloadable)
+ ) {
+ throw new IPF_ORM_Exception("Couldn't set eventlistener. EventListeners should implement either IPF_ORM_EventListener_Interface or IPF_ORM_Overloadable");
+ }
+ $this->attributes[IPF_ORM::ATTR_LISTENER] = $listener;
+
+ return $this;
+ }
+
+ public function getAttribute($attribute)
+ {
+ if (is_string($attribute)) {
+ $upper = strtoupper($attribute);
+
+ $const = 'IPF_ORM::ATTR_' . $upper;
+
+ if (defined($const)) {
+ $attribute = constant($const);
+ $this->_state = $attribute;
+ } else {
+ throw new IPF_ORM_Exception('Unknown attribute: "' . $attribute . '"');
+ }
+ }
+
+ $attribute = (int) $attribute;
+
+ if ($attribute < 0) {
+ throw new IPF_ORM_Exception('Unknown attribute.');
+ }
+
+ if (isset($this->attributes[$attribute])) {
+ return $this->attributes[$attribute];
+ }
+
+ if (isset($this->parent)) {
+ return $this->parent->getAttribute($attribute);
+ }
+ return null;
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function setParent(IPF_ORM_Configurable $component)
+ {
+ $this->parent = $component;
+ }
+
+ public function getParent()
+ {
+ return $this->parent;
+ }
+}
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Connection extends IPF_ORM_Configurable implements Countable, IteratorAggregate
+{
+ protected $dbh;
+ protected $tables = array();
+ protected $_name;
+ protected $driverName;
+ protected $isConnected = false;
+ protected $supported = array();
+ protected $pendingAttributes = array();
+
+ private $modules = array('transaction' => false,
+ 'expression' => false,
+ 'dataDict' => false,
+ 'export' => false,
+ 'import' => false,
+ 'sequence' => false,
+ 'unitOfWork' => false,
+ 'formatter' => false,
+ 'util' => false,
+ );
+
+ protected $properties = array('sql_comments' => array(array('start' => '--', 'end' => "\n", 'escape' => false),
+ array('start' => '/*', 'end' => '*/', 'escape' => false)),
+ 'identifier_quoting' => array('start' => '"', 'end' => '"','escape' => '"'),
+ 'string_quoting' => array('start' => "'",
+ 'end' => "'",
+ 'escape' => false,
+ 'escape_pattern' => false),
+ 'wildcards' => array('%', '_'),
+ 'varchar_max_length' => 255,
+ );
+
+ protected $serverInfo = array();
+ protected $options = array();
+ private static $availableDrivers = array(
+ 'Mysql',
+ 'Pgsql',
+ 'Oracle',
+ 'Informix',
+ 'Mssql',
+ 'Sqlite',
+ 'Firebird'
+ );
+ protected $_count = 0;
+
+ public function __construct(IPF_ORM_Manager $manager, $adapter, $user = null, $pass = null)
+ {
+ if (is_object($adapter)) {
+ if ( ! ($adapter instanceof PDO) && ! in_array('IPF_ORM_Adapter_Interface', class_implements($adapter))) {
+ throw new IPF_ORM_Exception('First argument should be an instance of PDO or implement IPF_ORM_Adapter_Interface');
+ }
+ $this->dbh = $adapter;
+
+ $this->isConnected = true;
+
+ } else if (is_array($adapter)) {
+ $this->pendingAttributes[IPF_ORM::ATTR_DRIVER_NAME] = $adapter['scheme'];
+
+ $this->options['dsn'] = $adapter['dsn'];
+ $this->options['username'] = $adapter['user'];
+ $this->options['password'] = $adapter['pass'];
+
+ $this->options['other'] = array();
+ if (isset($adapter['other'])) {
+ $this->options['other'] = array(IPF_ORM::ATTR_PERSISTENT => $adapter['persistent']);
+ }
+ }
+
+ $this->setParent($manager);
+
+ $this->setAttribute(IPF_ORM::ATTR_CASE, IPF_ORM::CASE_NATURAL);
+ $this->setAttribute(IPF_ORM::ATTR_ERRMODE, IPF_ORM::ERRMODE_EXCEPTION);
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->onOpen($this);
+ }
+
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ public function getOption($option)
+ {
+ if (isset($this->options[$option])) {
+ return $this->options[$option];
+ }
+ }
+
+ public function setOption($option, $value)
+ {
+ return $this->options[$option] = $value;
+ }
+
+ public function getAttribute($attribute)
+ {
+ if (is_string($attribute)) {
+ $stringAttribute = $attribute;
+ $attribute = $this->getAttributeFromString($attribute);
+ }
+
+ if ($attribute >= 100) {
+ if ( ! isset($this->attributes[$attribute])) {
+ return parent::getAttribute($attribute);
+ }
+ return $this->attributes[$attribute];
+ }
+
+ if ($this->isConnected) {
+ try {
+ return $this->dbh->getAttribute($attribute);
+ } catch (Exception $e) {
+ throw new IPF_ORM_Exception('Attribute ' . $attribute . ' not found.');
+ }
+ } else {
+ if ( ! isset($this->pendingAttributes[$attribute])) {
+ $this->connect();
+ $this->getAttribute($attribute);
+ }
+
+ return $this->pendingAttributes[$attribute];
+ }
+ }
+
+ public static function getAvailableDrivers()
+ {
+ return PDO::getAvailableDrivers();
+ }
+
+ public function setAttribute($attribute, $value)
+ {
+ if (is_string($attribute)) {
+ $attributeString = $attribute;
+ $attribute = parent::getAttributeFromString($attribute);
+ }
+
+ if (is_string($value) && isset($attributeString)) {
+ $value = parent::getAttributeValueFromString($attributeString, $value);
+ }
+
+ if ($attribute >= 100) {
+ parent::setAttribute($attribute, $value);
+ } else {
+ if ($this->isConnected) {
+ $this->dbh->setAttribute($attribute, $value);
+ } else {
+ $this->pendingAttributes[$attribute] = $value;
+ }
+ }
+
+ return $this;
+ }
+
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ public function setName($name)
+ {
+ $this->_name = $name;
+ }
+
+ public function getDriverName()
+ {
+ return $this->driverName;
+ }
+
+ public function __get($name)
+ {
+ if (isset($this->properties[$name])) {
+ return $this->properties[$name];
+ }
+
+ if ( ! isset($this->modules[$name])) {
+ throw new IPF_ORM_Exception('Unknown module / property ' . $name);
+ }
+ if ($this->modules[$name] === false) {
+ switch ($name) {
+ case 'unitOfWork':
+ $this->modules[$name] = new IPF_ORM_Connection_UnitOfWork($this);
+ break;
+ case 'formatter':
+ $this->modules[$name] = new IPF_ORM_Formatter($this);
+ break;
+ default:
+ $class = 'IPF_ORM_' . ucwords($name) . '_' . $this->getDriverName();
+ $this->modules[$name] = new $class($this);
+ }
+ }
+
+ return $this->modules[$name];
+ }
+
+ public function getManager()
+ {
+ return $this->getParent();
+ }
+
+ public function getDbh()
+ {
+ $this->connect();
+
+ return $this->dbh;
+ }
+
+ public function connect()
+ {
+ if ($this->isConnected) {
+ return false;
+ }
+
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_CONNECT);
+
+ $this->getListener()->preConnect($event);
+
+ $e = explode(':', $this->options['dsn']);
+ $found = false;
+
+ if (extension_loaded('pdo')) {
+ if (in_array($e[0], PDO::getAvailableDrivers())) {
+ try {
+ $this->dbh = new PDO($this->options['dsn'], $this->options['username'],
+ (!$this->options['password'] ? '':$this->options['password']), $this->options['other']);
+
+ $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ } catch (PDOException $e) {
+ throw new IPF_ORM_Exception('PDO Connection Error: ' . $e->getMessage());
+ }
+ $found = true;
+ }
+ }
+
+ if ( !$found) {
+ throw new IPF_Exception_Panic("Couldn't locate driver named " . $e[0]);
+ }
+
+ // attach the pending attributes to adapter
+ foreach($this->pendingAttributes as $attr => $value) {
+ // some drivers don't support setting this so we just skip it
+ if ($attr == IPF_ORM::ATTR_DRIVER_NAME) {
+ continue;
+ }
+ $this->dbh->setAttribute($attr, $value);
+ }
+
+ $this->isConnected = true;
+
+ $this->getListener()->postConnect($event);
+ return true;
+ }
+
+ public function incrementQueryCount()
+ {
+ $this->_count++;
+ }
+
+ public function driverName($name)
+ {
+ }
+
+ public function supports($feature)
+ {
+ return (isset($this->supported[$feature])
+ && ($this->supported[$feature] === 'emulated'
+ || $this->supported[$feature]));
+ }
+
+ public function replace(IPF_ORM_Table $table, array $fields, array $keys)
+ {
+ if (empty($keys)) {
+ throw new IPF_ORM_Exception('Not specified which fields are keys');
+ }
+ $identifier = (array) $table->getIdentifier();
+ $condition = array();
+
+ foreach ($fields as $fieldName => $value) {
+ if (in_array($fieldName, $keys)) {
+ if ($value !== null) {
+ $condition[] = $table->getColumnName($fieldName) . ' = ?';
+ $conditionValues[] = $value;
+ }
+ }
+ }
+
+ $affectedRows = 0;
+ if ( ! empty($condition) && ! empty($conditionValues)) {
+ $query = 'DELETE FROM ' . $this->quoteIdentifier($table->getTableName())
+ . ' WHERE ' . implode(' AND ', $condition);
+
+ $affectedRows = $this->exec($query, $conditionValues);
+ }
+
+ $this->insert($table, $fields);
+
+ $affectedRows++;
+
+ return $affectedRows;
+ }
+
+ public function delete(IPF_ORM_Table $table, array $identifier)
+ {
+ $tmp = array();
+
+ foreach (array_keys($identifier) as $id) {
+ $tmp[] = $this->quoteIdentifier($table->getColumnName($id)) . ' = ?';
+ }
+
+ $query = 'DELETE FROM '
+ . $this->quoteIdentifier($table->getTableName())
+ . ' WHERE ' . implode(' AND ', $tmp);
+
+ return $this->exec($query, array_values($identifier));
+ }
+
+ public function update(IPF_ORM_Table $table, array $fields, array $identifier)
+ {
+ if (empty($fields)) {
+ return false;
+ }
+
+ $set = array();
+ foreach ($fields as $fieldName => $value) {
+ if ($value instanceof IPF_ORM_Expression) {
+ $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ' . $value->getSql();
+ unset($fields[$fieldName]);
+ } else {
+ $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ?';
+ }
+ }
+
+ $params = array_merge(array_values($fields), array_values($identifier));
+
+ $sql = 'UPDATE ' . $this->quoteIdentifier($table->getTableName())
+ . ' SET ' . implode(', ', $set)
+ . ' WHERE ' . implode(' = ? AND ', $table->getIdentifierColumnNames())
+ . ' = ?';
+
+ return $this->exec($sql, $params);
+ }
+
+ public function insert(IPF_ORM_Table $table, array $fields)
+ {
+ $tableName = $table->getTableName();
+
+ // column names are specified as array keys
+ $cols = array();
+ // the query VALUES will contain either expresions (eg 'NOW()') or ?
+ $a = array();
+ foreach ($fields as $fieldName => $value) {
+ $cols[] = $this->quoteIdentifier($table->getColumnName($fieldName));
+ if ($value instanceof IPF_ORM_Expression) {
+ $a[] = $value->getSql();
+ unset($fields[$fieldName]);
+ } else {
+ $a[] = '?';
+ }
+ }
+
+ // build the statement
+ $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName)
+ . ' (' . implode(', ', $cols) . ')'
+ . ' VALUES (' . implode(', ', $a) . ')';
+
+ return $this->exec($query, array_values($fields));
+ }
+
+ public function setCharset($charset)
+ {
+ }
+
+ public function quoteIdentifier($str, $checkOption = true)
+ {
+ // quick fix for the identifiers that contain a dot
+ if (strpos($str, '.')) {
+ $e = explode('.', $str);
+
+ return $this->formatter->quoteIdentifier($e[0], $checkOption) . '.'
+ . $this->formatter->quoteIdentifier($e[1], $checkOption);
+ }
+ return $this->formatter->quoteIdentifier($str, $checkOption);
+ }
+
+ public function quoteMultipleIdentifier($arr, $checkOption = true)
+ {
+ foreach ($arr as $k => $v) {
+ $arr[$k] = $this->quoteIdentifier($v, $checkOption);
+ }
+
+ return $arr;
+ }
+
+ public function convertBooleans($item)
+ {
+ return $this->formatter->convertBooleans($item);
+ }
+
+ public function quote($input, $type = null)
+ {
+ return $this->formatter->quote($input, $type);
+ }
+
+ public function setDateFormat($format = null)
+ {
+ }
+
+ public function fetchAll($statement, array $params = array())
+ {
+ return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC);
+ }
+
+ public function fetchOne($statement, array $params = array(), $colnum = 0)
+ {
+ return $this->execute($statement, $params)->fetchColumn($colnum);
+ }
+
+ public function fetchRow($statement, array $params = array())
+ {
+ return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_ASSOC);
+ }
+
+ public function fetchArray($statement, array $params = array())
+ {
+ return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_NUM);
+ }
+
+ public function fetchColumn($statement, array $params = array(), $colnum = 0)
+ {
+ return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_COLUMN, $colnum);
+ }
+
+ public function fetchAssoc($statement, array $params = array())
+ {
+ return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC);
+ }
+
+ public function fetchBoth($statement, array $params = array())
+ {
+ return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_BOTH);
+ }
+
+ public function query($query, array $params = array(), $hydrationMode = null)
+ {
+ $parser = new IPF_ORM_Query($this);
+ $res = $parser->query($query, $params, $hydrationMode);
+ $parser->free();
+
+ return $res;
+ }
+
+ public function prepare($statement)
+ {
+ $this->connect();
+
+ try {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_PREPARE, $statement);
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->prePrepare($event);
+
+ $stmt = false;
+
+ if ( ! $event->skipOperation) {
+ $stmt = $this->dbh->prepare($statement);
+ }
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->postPrepare($event);
+
+ return new IPF_ORM_Connection_Statement($this, $stmt);
+ } catch(IPF_ORM_Exception_Adapter $e) {
+ } catch(PDOException $e) { }
+
+ $this->rethrowException($e, $this);
+ }
+
+ public function queryOne($query, array $params = array())
+ {
+ $parser = new IPF_ORM_Query($this);
+
+ $coll = $parser->query($query, $params);
+ if ( ! $coll->contains(0)) {
+ return false;
+ }
+ return $coll[0];
+ }
+
+ public function select($query, $limit = 0, $offset = 0)
+ {
+ if ($limit > 0 || $offset > 0) {
+ $query = $this->modifyLimitQuery($query, $limit, $offset);
+ }
+ return $this->execute($query);
+ }
+
+ public function standaloneQuery($query, $params = array())
+ {
+ return $this->execute($query, $params);
+ }
+
+ public function execute($query, array $params = array())
+ {
+ $this->connect();
+
+ try {
+ if ( ! empty($params)) {
+ $stmt = $this->prepare($query);
+ $stmt->execute($params);
+
+ return $stmt;
+ } else {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_QUERY, $query, $params);
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->preQuery($event);
+
+ if ( ! $event->skipOperation) {
+ $stmt = $this->dbh->query($query);
+ $this->_count++;
+ }
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->postQuery($event);
+
+ return $stmt;
+ }
+ } catch (IPF_ORM_Exception_Adapter $e) {
+ } catch (PDOException $e) { }
+
+ $this->rethrowException($e, $this);
+ }
+
+ public function exec($query, array $params = array())
+ {
+ $this->connect();
+
+ try {
+ if ( ! empty($params)) {
+ $stmt = $this->prepare($query);
+ $stmt->execute($params);
+
+ return $stmt->rowCount();
+ } else {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_EXEC, $query, $params);
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->preExec($event);
+ if ( ! $event->skipOperation) {
+ $count = $this->dbh->exec($query);
+
+ $this->_count++;
+ }
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->postExec($event);
+
+ return $count;
+ }
+ } catch (IPF_ORM_Exception_Adapter $e) {
+ } catch (PDOException $e) { }
+
+ $this->rethrowException($e, $this);
+ }
+
+ public function rethrowException(Exception $e, $invoker)
+ {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_ERROR);
+
+ $this->getListener()->preError($event);
+
+ $name = 'IPF_ORM_Exception_' . $this->driverName;
+
+ $exc = new $name($e->getMessage(), (int) $e->getCode());
+ if ( ! is_array($e->errorInfo)) {
+ $e->errorInfo = array(null, null, null, null);
+ }
+ $exc->processErrorInfo($e->errorInfo);
+
+ if ($this->getAttribute(IPF_ORM::ATTR_THROW_EXCEPTIONS)) {
+ throw $exc;
+ }
+
+ $this->getListener()->postError($event);
+ }
+
+ public function hasTable($name)
+ {
+ return isset($this->tables[$name]);
+ }
+
+ public function getTable($name)
+ {
+ if (isset($this->tables[$name])) {
+ return $this->tables[$name];
+ }
+ $class = $name . 'Table';
+
+ if (class_exists($class, $this->getAttribute(IPF_ORM::ATTR_AUTOLOAD_TABLE_CLASSES)) &&
+ in_array('IPF_ORM_Table', class_parents($class))) {
+ $table = new $class($name, $this, true);
+ } else {
+ $table = new IPF_ORM_Table($name, $this, true);
+ }
+
+ $this->tables[$name] = $table;
+
+ return $table;
+ }
+
+ public function getTables()
+ {
+ return $this->tables;
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->tables);
+ }
+
+ public function count()
+ {
+ return $this->_count;
+ }
+
+ public function addTable(IPF_ORM_Table $table)
+ {
+ $name = $table->getComponentName();
+
+ if (isset($this->tables[$name])) {
+ return false;
+ }
+ $this->tables[$name] = $table;
+ return true;
+ }
+
+ public function create($name)
+ {
+ return $this->getTable($name)->create();
+ }
+
+ public function createQuery()
+ {
+ return new IPF_ORM_Query($this);
+ }
+
+ public function flush()
+ {
+ $this->beginInternalTransaction();
+ $this->unitOfWork->saveAll();
+ $this->commit();
+ }
+
+ public function clear()
+ {
+ foreach ($this->tables as $k => $table) {
+ $table->getRepository()->evictAll();
+ $table->clear();
+ }
+ }
+
+ public function evictTables()
+ {
+ $this->tables = array();
+ $this->exported = array();
+ }
+
+ public function close()
+ {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_CLOSE);
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->preClose($event);
+
+ $this->clear();
+
+ unset($this->dbh);
+ $this->isConnected = false;
+
+ $this->getAttribute(IPF_ORM::ATTR_LISTENER)->postClose($event);
+ }
+
+ public function getTransactionLevel()
+ {
+ return $this->transaction->getTransactionLevel();
+ }
+
+ public function errorCode()
+ {
+ $this->connect();
+
+ return $this->dbh->errorCode();
+ }
+
+ public function errorInfo()
+ {
+ $this->connect();
+
+ return $this->dbh->errorInfo();
+ }
+
+ public function getCacheDriver()
+ {
+ return $this->getResultCacheDriver();
+ }
+
+ public function getResultCacheDriver()
+ {
+ if ( ! $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE)) {
+ throw new IPF_ORM_Exception('Result Cache driver not initialized.');
+ }
+
+ return $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE);
+ }
+
+ public function getQueryCacheDriver()
+ {
+ if ( ! $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) {
+ throw new IPF_ORM_Exception('Query Cache driver not initialized.');
+ }
+
+ return $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE);
+ }
+
+ public function lastInsertId($table = null, $field = null)
+ {
+ return $this->sequence->lastInsertId($table, $field);
+ }
+
+ public function beginTransaction($savepoint = null)
+ {
+ return $this->transaction->beginTransaction($savepoint);
+ }
+
+ public function beginInternalTransaction($savepoint = null)
+ {
+ return $this->transaction->beginInternalTransaction($savepoint);
+ }
+
+ public function commit($savepoint = null)
+ {
+ return $this->transaction->commit($savepoint);
+ }
+
+ public function rollback($savepoint = null)
+ {
+ $this->transaction->rollback($savepoint);
+ }
+
+ public function createDatabase()
+ {
+ if ( ! $dsn = $this->getOption('dsn')) {
+ throw new IPF_ORM_Exception('You must create your IPF_ORM_Connection by using a valid IPF style dsn in order to use the create/drop database functionality');
+ }
+
+ // Parse pdo dsn so we are aware of the connection information parts
+ $info = $this->getManager()->parsePdoDsn($dsn);
+
+ // Get the temporary connection to issue the drop database command
+ $tmpConnection = $this->getTmpConnection($info);
+
+ try {
+ // Issue create database command
+ $tmpConnection->export->createDatabase($info['dbname']);
+ } catch (Exception $e) {}
+
+ // Close the temporary connection used to issue the drop database command
+ $this->getManager()->closeConnection($tmpConnection);
+
+ // Re-create IPF or PDO style dsn
+ if ($info['unix_socket']) {
+ $dsn = array($info['scheme'] . ':unix_socket=' . $info['unix_socket'] . ';dbname=' . $info['dbname'], $this->getOption('username'), $this->getOption('password'));
+ } else {
+ $dsn = $info['scheme'] . '://' . $this->getOption('username') . ':' . $this->getOption('password') . '@' . $info['host'] . '/' . $info['dbname'];
+ }
+
+ // Re-open connection with the newly created database
+ $this->getManager()->openConnection($dsn, $this->getName(), true);
+
+ if (isset($e)) {
+ return $e;
+ } else {
+ return 'Successfully created database for connection "' . $this->getName() . '" named "' . $info['dbname'] . '"';
+ }
+ }
+
+ public function dropDatabase()
+ {
+ if ( ! $dsn = $this->getOption('dsn')) {
+ throw new IPF_ORM_Exception('You must create your IPF_ORM_Connection by using a valid IPF style dsn in order to use the create/drop database functionality');
+ }
+
+ // Parse pdo dsn so we are aware of the connection information parts
+ $info = $this->getManager()->parsePdoDsn($dsn);
+
+ // Get the temporary connection to issue the drop database command
+ $tmpConnection = $this->getTmpConnection($info);
+
+ try {
+ // Issue drop database command
+ $tmpConnection->export->dropDatabase($info['dbname']);
+ } catch (Exception $e) {}
+
+ // Close the temporary connection used to issue the drop database command
+ $this->getManager()->closeConnection($tmpConnection);
+
+ // Re-create IPF or PDO style dsn
+ if ($info['unix_socket']) {
+ $dsn = array($info['scheme'] . ':unix_socket=' . $info['unix_socket'] . ';dbname=' . $info['dbname'], $this->getOption('username'), $this->getOption('password'));
+ } else {
+ $dsn = $info['scheme'] . '://' . $this->getOption('username') . ':' . $this->getOption('password') . '@' . $info['host'] . '/' . $info['dbname'];
+ }
+
+ // Re-open connection with the newly created database
+ $this->getManager()->openConnection($dsn, $this->getName(), true);
+
+ if (isset($e)) {
+ return $e;
+ } else {
+ return 'Successfully dropped database for connection "' . $this->getName() . '" named "' . $info['dbname'] . '"';
+ }
+ }
+
+ public function getTmpConnection($info)
+ {
+ if ($info['unix_socket']) {
+ $pdoDsn = $info['scheme'] . ':unix_socket=' . $info['unix_socket'];
+ } else {
+ $pdoDsn = $info['scheme'] . ':host=' . $info['host'];
+ }
+
+ if (isset($this->export->tmpConnectionDatabase) && $this->export->tmpConnectionDatabase) {
+ $pdoDsn .= ';dbname=' . $this->export->tmpConnectionDatabase;
+ }
+
+ $username = $this->getOption('username');
+ $password = $this->getOption('password');
+
+ return $this->getManager()->openConnection(new PDO($pdoDsn, $username, $password), 'ipf_tmp_connection', false);
+ }
+
+ public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false)
+ {
+ return $query;
+ }
+
+ public function modifyLimitSubquery(IPF_ORM_Table $rootTable, $query, $limit = false,
+ $offset = false, $isManip = false)
+ {
+ return $this->modifyLimitQuery($query, $limit, $offset, $isManip);
+ }
+
+ public function __toString()
+ {
+ return IPF_ORM_Utils::getConnectionAsString($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Connection_Module
+{
+ protected $conn;
+ protected $moduleName;
+
+ public function __construct($conn = null)
+ {
+ if ( ! ($conn instanceof IPF_ORM_Connection)) {
+ $conn = IPF_ORM_Manager::getInstance()->getCurrentConnection();
+ }
+ $this->conn = $conn;
+
+ $e = explode('_', get_class($this));
+
+ $this->moduleName = $e[1];
+ }
+
+ public function getConnection()
+ {
+ return $this->conn;
+ }
+
+ public function getModuleName()
+ {
+ return $this->moduleName;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Connection_Mysql extends IPF_ORM_Connection
+{
+ protected $driverName = 'Mysql';
+
+ public function __construct(IPF_ORM_Manager $manager, $adapter)
+ {
+ $this->setAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE, 'INNODB');
+ $this->supported = array(
+ 'sequences' => 'emulated',
+ 'indexes' => true,
+ 'affected_rows' => true,
+ 'transactions' => true,
+ 'savepoints' => false,
+ 'summary_functions' => true,
+ 'order_by_text' => true,
+ 'current_id' => 'emulated',
+ 'limit_queries' => true,
+ 'LOBs' => true,
+ 'replace' => true,
+ 'sub_selects' => true,
+ 'auto_increment' => true,
+ 'primary_key' => true,
+ 'result_introspection' => true,
+ 'prepared_statements' => 'emulated',
+ 'identifier_quoting' => true,
+ 'pattern_escaping' => true
+ );
+
+ $this->properties['string_quoting'] = array('start' => "'",
+ 'end' => "'",
+ 'escape' => '\\',
+ 'escape_pattern' => '\\');
+
+ $this->properties['identifier_quoting'] = array('start' => '`',
+ 'end' => '`',
+ 'escape' => '`');
+
+ $this->properties['sql_comments'] = array(
+ array('start' => '-- ', 'end' => "\n", 'escape' => false),
+ array('start' => '#', 'end' => "\n", 'escape' => false),
+ array('start' => '/*', 'end' => '*/', 'escape' => false),
+ );
+
+ $this->properties['varchar_max_length'] = 255;
+
+ parent::__construct($manager, $adapter);
+ }
+
+ public function connect()
+ {
+ $connected = parent::connect();
+ $this->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
+ return $connected;
+ }
+
+ public function getDatabaseName()
+ {
+ return $this->fetchOne('SELECT DATABASE()');
+ }
+
+ public function setCharset($charset)
+ {
+ $query = 'SET NAMES ' . $this->quote($charset);
+ $this->exec($query);
+ }
+
+ public function replace(IPF_ORM_Table $table, array $fields, array $keys)
+ {
+ if (empty($keys)) {
+ throw new IPF_ORM_Exception('Not specified which fields are keys');
+ }
+ $columns = array();
+ $values = array();
+ $params = array();
+ foreach ($fields as $fieldName => $value) {
+ $columns[] = $table->getColumnName($fieldName);
+ $values[] = '?';
+ $params[] = $value;
+ }
+ $query = 'REPLACE INTO ' . $table->getTableName() . ' (' . implode(',', $columns) . ') VALUES (' . implode(',', $values) . ')';
+ return $this->exec($query, $params);
+ }
+
+ public function modifyLimitQuery($query, $limit = false,$offset = false,$isManip=false)
+ {
+ $limit = (int) $limit;
+ $offset = (int) $offset;
+
+ if ($limit && $offset) {
+ $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } elseif ($limit && ! $offset) {
+ $query .= ' LIMIT ' . $limit;
+ } elseif ( ! $limit && $offset) {
+ $query .= ' LIMIT 999999999999 OFFSET ' . $offset;
+ }
+ return $query;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Connection_Statement implements IPF_ORM_Adapter_Statement_Interface
+{
+ protected $_conn;
+ protected $_stmt;
+ public function __construct(IPF_ORM_Connection $conn, $stmt)
+ {
+ $this->_conn = $conn;
+ $this->_stmt = $stmt;
+
+ if ($stmt === false) {
+ throw new IPF_ORM_Exception('Unknown statement object given.');
+ }
+ }
+
+ public function getConnection()
+ {
+ return $this->_conn;
+ }
+ public function getStatement()
+ {
+ return $this->_stmt;
+ }
+ public function getQuery()
+ {
+ return $this->_stmt->queryString;
+ }
+
+ public function bindColumn($column, $param, $type = null)
+ {
+ if ($type === null) {
+ return $this->_stmt->bindColumn($column, $param);
+ } else {
+ return $this->_stmt->bindColumn($column, $param, $type);
+ }
+ }
+
+ public function bindValue($param, $value, $type = null)
+ {
+ if ($type === null) {
+ return $this->_stmt->bindValue($param, $value);
+ } else {
+ return $this->_stmt->bindValue($param, $value, $type);
+ }
+ }
+
+ public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array())
+ {
+ if ($type === null) {
+ return $this->_stmt->bindParam($column, $variable);
+ } else {
+ return $this->_stmt->bindParam($column, $variable, $type, $length, $driverOptions);
+ }
+ }
+
+ public function closeCursor()
+ {
+ return $this->_stmt->closeCursor();
+ }
+
+ public function columnCount()
+ {
+ return $this->_stmt->columnCount();
+ }
+
+ public function errorCode()
+ {
+ return $this->_stmt->errorCode();
+ }
+
+ public function errorInfo()
+ {
+ return $this->_stmt->errorInfo();
+ }
+
+ public function execute($params = null)
+ {
+ try {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_EXECUTE, $this->getQuery(), $params);
+ $this->_conn->getListener()->preStmtExecute($event);
+
+ $result = true;
+ if ( ! $event->skipOperation) {
+ $result = $this->_stmt->execute($params);
+ $this->_conn->incrementQueryCount();
+ }
+
+ $this->_conn->getListener()->postStmtExecute($event);
+
+ return $result;
+ } catch (PDOException $e) {
+ } catch (IPF_ORM_Exception_Adapter $e) {
+ }
+
+ $this->_conn->rethrowException($e, $this);
+
+ return false;
+ }
+
+ public function fetch($fetchMode = IPF_ORM::FETCH_BOTH,
+ $cursorOrientation = IPF_ORM::FETCH_ORI_NEXT,
+ $cursorOffset = null)
+ {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCH, $this->getQuery());
+
+ $event->fetchMode = $fetchMode;
+ $event->cursorOrientation = $cursorOrientation;
+ $event->cursorOffset = $cursorOffset;
+
+ $data = $this->_conn->getListener()->preFetch($event);
+
+ if ( ! $event->skipOperation) {
+ $data = $this->_stmt->fetch($fetchMode, $cursorOrientation, $cursorOffset);
+ }
+
+ $this->_conn->getListener()->postFetch($event);
+
+ return $data;
+ }
+
+ public function fetchAll($fetchMode = IPF_ORM::FETCH_BOTH,
+ $columnIndex = null)
+ {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCHALL, $this->getQuery());
+ $event->fetchMode = $fetchMode;
+ $event->columnIndex = $columnIndex;
+
+ $this->_conn->getListener()->preFetchAll($event);
+
+ if ( ! $event->skipOperation) {
+ if ($columnIndex !== null) {
+ $data = $this->_stmt->fetchAll($fetchMode, $columnIndex);
+ } else {
+ $data = $this->_stmt->fetchAll($fetchMode);
+ }
+
+ $event->data = $data;
+ }
+
+ $this->_conn->getListener()->postFetchAll($event);
+
+ return $data;
+ }
+
+ public function fetchColumn($columnIndex = 0)
+ {
+ return $this->_stmt->fetchColumn($columnIndex);
+ }
+
+ public function fetchObject($className = 'stdClass', $args = array())
+ {
+ return $this->_stmt->fetchObject($className, $args);
+ }
+
+ public function getAttribute($attribute)
+ {
+ return $this->_stmt->getAttribute($attribute);
+ }
+
+ public function getColumnMeta($column)
+ {
+ return $this->_stmt->getColumnMeta($column);
+ }
+
+ public function nextRowset()
+ {
+ return $this->_stmt->nextRowset();
+ }
+
+ public function rowCount()
+ {
+ return $this->_stmt->rowCount();
+ }
+
+ public function setAttribute($attribute, $value)
+ {
+ return $this->_stmt->setAttribute($attribute, $value);
+ }
+
+ public function setFetchMode($mode, $arg1 = null, $arg2 = null)
+ {
+ return $this->_stmt->setFetchMode($mode, $arg1, $arg2);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Connection_UnitOfWork extends IPF_ORM_Connection_Module
+{
+ public function saveGraph(IPF_ORM_Record $record)
+ {
+ $conn = $this->getConnection();
+
+ $state = $record->state();
+ if ($state === IPF_ORM_Record::STATE_LOCKED) {
+ return false;
+ }
+
+ $record->state(IPF_ORM_Record::STATE_LOCKED);
+
+ $conn->beginInternalTransaction();
+ $saveLater = $this->saveRelated($record);
+
+ $record->state($state);
+
+ if ($record->isValid()) {
+ $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE);
+ $record->preSave($event);
+ $record->getTable()->getRecordListener()->preSave($event);
+ $state = $record->state();
+
+ if ( ! $event->skipOperation) {
+ switch ($state) {
+ case IPF_ORM_Record::STATE_TDIRTY:
+ $this->insert($record);
+ break;
+ case IPF_ORM_Record::STATE_DIRTY:
+ case IPF_ORM_Record::STATE_PROXY:
+ $this->update($record);
+ break;
+ case IPF_ORM_Record::STATE_CLEAN:
+ case IPF_ORM_Record::STATE_TCLEAN:
+ break;
+ }
+ }
+
+ // NOTE: what about referential integrity issues?
+ foreach ($record->getPendingDeletes() as $pendingDelete) {
+ $pendingDelete->delete();
+ }
+
+ $record->getTable()->getRecordListener()->postSave($event);
+ $record->postSave($event);
+ } else {
+ $conn->transaction->addInvalid($record);
+ }
+
+ $state = $record->state();
+
+ $record->state(IPF_ORM_Record::STATE_LOCKED);
+
+ foreach ($saveLater as $fk) {
+ $alias = $fk->getAlias();
+
+ if ($record->hasReference($alias)) {
+ $obj = $record->$alias;
+
+ // check that the related object is not an instance of IPF_ORM_Null
+ if ( ! ($obj instanceof IPF_ORM_Null)) {
+ $obj->save($conn);
+ }
+ }
+ }
+
+ // save the MANY-TO-MANY associations
+ $this->saveAssociations($record);
+
+ $record->state($state);
+
+ $conn->commit();
+
+ return true;
+ }
+
+ public function save(IPF_ORM_Record $record)
+ {
+ $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE);
+
+ $record->preSave($event);
+
+ $record->getTable()->getRecordListener()->preSave($event);
+
+ if ( ! $event->skipOperation) {
+ switch ($record->state()) {
+ case IPF_ORM_Record::STATE_TDIRTY:
+ $this->insert($record);
+ break;
+ case IPF_ORM_Record::STATE_DIRTY:
+ case IPF_ORM_Record::STATE_PROXY:
+ $this->update($record);
+ break;
+ case IPF_ORM_Record::STATE_CLEAN:
+ case IPF_ORM_Record::STATE_TCLEAN:
+ // do nothing
+ break;
+ }
+ }
+
+ $record->getTable()->getRecordListener()->postSave($event);
+
+ $record->postSave($event);
+ }
+
+ public function delete(IPF_ORM_Record $record)
+ {
+ $deletions = array();
+ $this->_collectDeletions($record, $deletions);
+ return $this->_executeDeletions($deletions);
+ }
+
+ private function _collectDeletions(IPF_ORM_Record $record, array &$deletions)
+ {
+ if ( ! $record->exists()) {
+ return;
+ }
+
+ $deletions[$record->getOid()] = $record;
+ $this->_cascadeDelete($record, $deletions);
+ }
+
+ private function _executeDeletions(array $deletions)
+ {
+ // collect class names
+ $classNames = array();
+ foreach ($deletions as $record) {
+ $classNames[] = $record->getTable()->getComponentName();
+ }
+ $classNames = array_unique($classNames);
+
+ // order deletes
+ $executionOrder = $this->buildFlushTree($classNames);
+
+ // execute
+ try {
+ $this->conn->beginInternalTransaction();
+
+ for ($i = count($executionOrder) - 1; $i >= 0; $i--) {
+ $className = $executionOrder[$i];
+ $table = $this->conn->getTable($className);
+
+ // collect identifiers
+ $identifierMaps = array();
+ $deletedRecords = array();
+ foreach ($deletions as $oid => $record) {
+ if ($record->getTable()->getComponentName() == $className) {
+ $veto = $this->_preDelete($record);
+ if ( ! $veto) {
+ $identifierMaps[] = $record->identifier();
+ $deletedRecords[] = $record;
+ unset($deletions[$oid]);
+ }
+ }
+ }
+
+ if (count($deletedRecords) < 1) {
+ continue;
+ }
+
+ // extract query parameters (only the identifier values are of interest)
+ $params = array();
+ $columnNames = array();
+ foreach ($identifierMaps as $idMap) {
+ while (list($fieldName, $value) = each($idMap)) {
+ $params[] = $value;
+ $columnNames[] = $table->getColumnName($fieldName);
+ }
+ }
+ $columnNames = array_unique($columnNames);
+
+ // delete
+ $tableName = $table->getTableName();
+ $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE ";
+
+ if ($table->isIdentifierComposite()) {
+ $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps));
+ $this->conn->exec($sql, $params);
+ } else {
+ $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params));
+ $this->conn->exec($sql, $params);
+ }
+
+ // adjust state, remove from identity map and inform postDelete listeners
+ foreach ($deletedRecords as $record) {
+ // currently just for bc!
+ $this->_deleteCTIParents($table, $record);
+ //--
+ $record->state(IPF_ORM_Record::STATE_TCLEAN);
+ $record->getTable()->removeRecord($record);
+ $this->_postDelete($record);
+ }
+ }
+
+ $this->conn->commit();
+ // trigger postDelete for records skipped during the deletion (veto!)
+ foreach ($deletions as $skippedRecord) {
+ $this->_postDelete($skippedRecord);
+ }
+
+ return true;
+ } catch (Exception $e) {
+ $this->conn->rollback();
+ throw $e;
+ }
+ }
+
+ private function _buildSqlSingleKeyCondition($columnNames, $numRecords)
+ {
+ $idColumn = $this->conn->quoteIdentifier($columnNames[0]);
+ return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?"));
+ }
+
+ private function _buildSqlCompositeKeyCondition($columnNames, $numRecords)
+ {
+ $singleCondition = "";
+ foreach ($columnNames as $columnName) {
+ $columnName = $this->conn->quoteIdentifier($columnName);
+ if ($singleCondition === "") {
+ $singleCondition .= "($columnName = ?";
+ } else {
+ $singleCondition .= " AND $columnName = ?";
+ }
+ }
+ $singleCondition .= ")";
+ $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition));
+
+ return $fullCondition;
+ }
+
+ protected function _cascadeDelete(IPF_ORM_Record $record, array &$deletions)
+ {
+ foreach ($record->getTable()->getRelations() as $relation) {
+ if ($relation->isCascadeDelete()) {
+ $fieldName = $relation->getAlias();
+ // if it's a xToOne relation and the related object is already loaded
+ // we don't need to refresh.
+ if ( ! ($relation->getType() == IPF_ORM_Relation::ONE && isset($record->$fieldName))) {
+ $record->refreshRelated($relation->getAlias());
+ }
+ $relatedObjects = $record->get($relation->getAlias());
+ if ($relatedObjects instanceof IPF_ORM_Record && $relatedObjects->exists()
+ && ! isset($deletions[$relatedObjects->getOid()])) {
+ $this->_collectDeletions($relatedObjects, $deletions);
+ } else if ($relatedObjects instanceof IPF_ORM_Collection && count($relatedObjects) > 0) {
+ // cascade the delete to the other objects
+ foreach ($relatedObjects as $object) {
+ if ( ! isset($deletions[$object->getOid()])) {
+ $this->_collectDeletions($object, $deletions);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public function saveRelated(IPF_ORM_Record $record)
+ {
+ $saveLater = array();
+ foreach ($record->getReferences() as $k => $v) {
+ $rel = $record->getTable()->getRelation($k);
+
+ $local = $rel->getLocal();
+ $foreign = $rel->getForeign();
+
+ if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+ $saveLater[$k] = $rel;
+ } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+ // ONE-TO-ONE relationship
+ $obj = $record->get($rel->getAlias());
+
+ // Protection against infinite function recursion before attempting to save
+ if ($obj instanceof IPF_ORM_Record && $obj->isModified()) {
+ $obj->save($this->conn);
+
+ /** Can this be removed?
+ $id = array_values($obj->identifier());
+
+ foreach ((array) $rel->getLocal() as $k => $field) {
+ $record->set($field, $id[$k]);
+ }
+ */
+ }
+ }
+ }
+
+ return $saveLater;
+ }
+
+ public function saveAssociations(IPF_ORM_Record $record)
+ {
+ foreach ($record->getReferences() as $k => $v) {
+ $rel = $record->getTable()->getRelation($k);
+
+ if ($rel instanceof IPF_ORM_Relation_Association) {
+ $v->save($this->conn);
+
+ $assocTable = $rel->getAssociationTable();
+ foreach ($v->getDeleteDiff() as $r) {
+ $query = 'DELETE FROM ' . $assocTable->getTableName()
+ . ' WHERE ' . $rel->getForeign() . ' = ?'
+ . ' AND ' . $rel->getLocal() . ' = ?';
+
+ $this->conn->execute($query, array($r->getIncremented(), $record->getIncremented()));
+ }
+
+ foreach ($v->getInsertDiff() as $r) {
+ $assocRecord = $assocTable->create();
+ $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r);
+ $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record);
+ $this->saveGraph($assocRecord);
+ }
+ }
+ }
+ }
+
+ private function _preDelete(IPF_ORM_Record $record)
+ {
+ $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE);
+ $record->preDelete($event);
+ $record->getTable()->getRecordListener()->preDelete($event);
+
+ return $event->skipOperation;
+ }
+
+ private function _postDelete(IPF_ORM_Record $record)
+ {
+ $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE);
+ $record->postDelete($event);
+ $record->getTable()->getRecordListener()->postDelete($event);
+ }
+
+ public function saveAll()
+ {
+ // get the flush tree
+ $tree = $this->buildFlushTree($this->conn->getTables());
+
+ // save all records
+ foreach ($tree as $name) {
+ $table = $this->conn->getTable($name);
+ foreach ($table->getRepository() as $record) {
+ $this->saveGraph($record);
+ }
+ }
+ }
+
+ public function update(IPF_ORM_Record $record)
+ {
+ $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_UPDATE);
+ $record->preUpdate($event);
+ $table = $record->getTable();
+ $table->getRecordListener()->preUpdate($event);
+
+ if ( ! $event->skipOperation) {
+ $identifier = $record->identifier();
+ if ($table->getOption('joinedParents')) {
+ // currrently just for bc!
+ $this->_updateCTIRecord($table, $record);
+ //--
+ } else {
+ $array = $record->getPrepared();
+ $this->conn->update($table, $array, $identifier);
+ }
+ $record->assignIdentifier(true);
+ }
+
+ $table->getRecordListener()->postUpdate($event);
+
+ $record->postUpdate($event);
+
+ return true;
+ }
+
+ public function insert(IPF_ORM_Record $record)
+ {
+ // listen the onPreInsert event
+ $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_INSERT);
+ $record->preInsert($event);
+ $table = $record->getTable();
+ $table->getRecordListener()->preInsert($event);
+
+ if ( ! $event->skipOperation) {
+ if ($table->getOption('joinedParents')) {
+ // just for bc!
+ $this->_insertCTIRecord($table, $record);
+ //--
+ } else {
+ $this->processSingleInsert($record);
+ }
+ }
+
+ $table->addRecord($record);
+ $table->getRecordListener()->postInsert($event);
+ $record->postInsert($event);
+
+ return true;
+ }
+
+ public function processSingleInsert(IPF_ORM_Record $record)
+ {
+ $fields = $record->getPrepared();
+ $table = $record->getTable();
+
+ // Populate fields with a blank array so that a blank records can be inserted
+ if (empty($fields)) {
+ foreach ($table->getFieldNames() as $field) {
+ $fields[$field] = null;
+ }
+ }
+
+ $identifier = (array) $table->getIdentifier();
+
+ $seq = $record->getTable()->sequenceName;
+
+ if ( ! empty($seq)) {
+ $id = $this->conn->sequence->nextId($seq);
+ $seqName = $table->getIdentifier();
+ $fields[$seqName] = $id;
+
+ $record->assignIdentifier($id);
+ }
+
+ $this->conn->insert($table, $fields);
+
+ if (empty($seq) && count($identifier) == 1 && $identifier[0] == $table->getIdentifier() &&
+ $table->getIdentifierType() != IPF_ORM::IDENTIFIER_NATURAL) {
+ if (strtolower($this->conn->getDriverName()) == 'pgsql') {
+ $seq = $table->getTableName() . '_' . $identifier[0];
+ }
+
+ $id = $this->conn->sequence->lastInsertId($seq);
+
+ if ( ! $id) {
+ throw new IPF_ORM_Exception("Couldn't get last insert identifier.");
+ }
+ $record->assignIdentifier($id);
+ } else {
+ $record->assignIdentifier(true);
+ }
+ }
+
+ public function buildFlushTree(array $tables)
+ {
+ // determine classes to order. only necessary because the $tables param
+ // can contain strings or table objects...
+ $classesToOrder = array();
+ foreach ($tables as $table) {
+ if ( ! ($table instanceof IPF_ORM_Table)) {
+ $table = $this->conn->getTable($table, false);
+ }
+ $classesToOrder[] = $table->getComponentName();
+ }
+ $classesToOrder = array_unique($classesToOrder);
+
+ if (count($classesToOrder) < 2) {
+ return $classesToOrder;
+ }
+
+ // build the correct order
+ $flushList = array();
+ foreach ($classesToOrder as $class) {
+ $table = $this->conn->getTable($class, false);
+ $currentClass = $table->getComponentName();
+
+ $index = array_search($currentClass, $flushList);
+
+ if ($index === false) {
+ //echo "adding $currentClass to flushlist";
+ $flushList[] = $currentClass;
+ $index = max(array_keys($flushList));
+ }
+
+ $rels = $table->getRelations();
+
+ // move all foreignkey relations to the beginning
+ foreach ($rels as $key => $rel) {
+ if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+ unset($rels[$key]);
+ array_unshift($rels, $rel);
+ }
+ }
+
+ foreach ($rels as $rel) {
+ $relatedClassName = $rel->getTable()->getComponentName();
+
+ if ( ! in_array($relatedClassName, $classesToOrder)) {
+ continue;
+ }
+
+ $relatedCompIndex = array_search($relatedClassName, $flushList);
+ $type = $rel->getType();
+
+ // skip self-referenced relations
+ if ($relatedClassName === $currentClass) {
+ continue;
+ }
+
+ if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+ // the related component needs to come after this component in
+ // the list (since it holds the fk)
+
+ if ($relatedCompIndex !== false) {
+ // the component is already in the list
+ if ($relatedCompIndex >= $index) {
+ // it's already in the right place
+ continue;
+ }
+
+ unset($flushList[$index]);
+ // the related comp has the fk. so put "this" comp immediately
+ // before it in the list
+ array_splice($flushList, $relatedCompIndex, 0, $currentClass);
+ $index = $relatedCompIndex;
+ } else {
+ $flushList[] = $relatedClassName;
+ }
+
+ } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+ // the related component needs to come before the current component
+ // in the list (since this component holds the fk).
+
+ if ($relatedCompIndex !== false) {
+ // already in flush list
+ if ($relatedCompIndex <= $index) {
+ // it's in the right place
+ continue;
+ }
+
+ unset($flushList[$relatedCompIndex]);
+ // "this" comp has the fk. so put the related comp before it
+ // in the list
+ array_splice($flushList, $index, 0, $relatedClassName);
+ } else {
+ array_unshift($flushList, $relatedClassName);
+ $index++;
+ }
+ } else if ($rel instanceof IPF_ORM_Relation_Association) {
+ // the association class needs to come after both classes
+ // that are connected through it in the list (since it holds
+ // both fks)
+
+ $assocTable = $rel->getAssociationFactory();
+ $assocClassName = $assocTable->getComponentName();
+
+ if ($relatedCompIndex !== false) {
+ unset($flushList[$relatedCompIndex]);
+ }
+
+ array_splice($flushList, $index, 0, $relatedClassName);
+ $index++;
+
+ $index3 = array_search($assocClassName, $flushList);
+
+ if ($index3 !== false) {
+ if ($index3 >= $index) {
+ continue;
+ }
+
+ unset($flushList[$index]);
+ array_splice($flushList, $index3, 0, $assocClassName);
+ $index = $relatedCompIndex;
+ } else {
+ $flushList[] = $assocClassName;
+ }
+ }
+ }
+ }
+
+ return array_values($flushList);
+ }
+
+ private function _deleteCTIParents(IPF_ORM_Table $table, $record)
+ {
+ if ($table->getOption('joinedParents')) {
+ foreach (array_reverse($table->getOption('joinedParents')) as $parent) {
+ $parentTable = $table->getConnection()->getTable($parent);
+ $this->conn->delete($parentTable, $record->identifier());
+ }
+ }
+ }
+
+ private function _insertCTIRecord(IPF_ORM_Table $table, IPF_ORM_Record $record)
+ {
+ $dataSet = $this->_formatDataSet($record);
+ $component = $table->getComponentName();
+
+ $classes = $table->getOption('joinedParents');
+ $classes[] = $component;
+
+ foreach ($classes as $k => $parent) {
+ if ($k === 0) {
+ $rootRecord = new $parent();
+ $rootRecord->merge($dataSet[$parent]);
+ $this->processSingleInsert($rootRecord);
+ $record->assignIdentifier($rootRecord->identifier());
+ } else {
+ foreach ((array) $rootRecord->identifier() as $id => $value) {
+ $dataSet[$parent][$id] = $value;
+ }
+
+ $this->conn->insert($this->conn->getTable($parent), $dataSet[$parent]);
+ }
+ }
+ }
+
+ private function _updateCTIRecord(IPF_ORM_Table $table, IPF_ORM_Record $record)
+ {
+ $identifier = $record->identifier();
+ $dataSet = $this->_formatDataSet($record);
+
+ $component = $table->getComponentName();
+
+ $classes = $table->getOption('joinedParents');
+ $classes[] = $component;
+
+ foreach ($record as $field => $value) {
+ if ($value instanceof IPF_ORM_Record) {
+ if ( ! $value->exists()) {
+ $value->save();
+ }
+ $record->set($field, $value->getIncremented());
+ }
+ }
+
+ foreach ($classes as $class) {
+ $parentTable = $this->conn->getTable($class);
+
+ if ( ! array_key_exists($class, $dataSet)) {
+ continue;
+ }
+
+ $this->conn->update($this->conn->getTable($class), $dataSet[$class], $identifier);
+ }
+ }
+
+ private function _formatDataSet(IPF_ORM_Record $record)
+ {
+ $table = $record->getTable();
+ $dataSet = array();
+ $component = $table->getComponentName();
+ $array = $record->getPrepared();
+
+ foreach ($table->getColumns() as $columnName => $definition) {
+ if ( ! isset($dataSet[$component])) {
+ $dataSet[$component] = array();
+ }
+
+ $fieldName = $table->getFieldName($columnName);
+ if (isset($definition['primary']) && $definition['primary']) {
+ continue;
+ }
+
+ if ( ! array_key_exists($fieldName, $array)) {
+ continue;
+ }
+
+ if (isset($definition['owner'])) {
+ $dataSet[$definition['owner']][$fieldName] = $array[$fieldName];
+ } else {
+ $dataSet[$component][$fieldName] = $array[$fieldName];
+ }
+ }
+
+ return $dataSet;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_DataDict extends IPF_ORM_Connection_Module
+{
+ public function compareDefinition($current, $previous)
+ {
+ $type = !empty($current['type']) ? $current['type'] : null;
+
+ if ( ! method_exists($this, "_compare{$type}Definition")) {
+ throw new IPF_ORM_Exception('type "'.$current['type'].'" is not yet supported');
+ }
+
+ if (empty($previous['type']) || $previous['type'] != $type) {
+ return $current;
+ }
+
+ $change = $this->{"_compare{$type}Definition"}($current, $previous);
+
+ if ($previous['type'] != $type) {
+ $change['type'] = true;
+ }
+
+ $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false;
+ $notnull = !empty($current['notnull']) ? $current['notnull'] : false;
+ if ($previous_notnull != $notnull) {
+ $change['notnull'] = true;
+ }
+
+ $previous_default = array_key_exists('default', $previous) ? $previous['default'] :
+ ($previous_notnull ? '' : null);
+ $default = array_key_exists('default', $current) ? $current['default'] :
+ ($notnull ? '' : null);
+ if ($previous_default !== $default) {
+ $change['default'] = true;
+ }
+
+ return $change;
+ }
+
+ public function parseBoolean($value)
+ {
+ // parse booleans
+ if ($value == 'true') {
+ $value = 1;
+ } elseif ($value == 'false') {
+ $value = 0;
+ }
+ return $value;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_DataDict_Mysql extends IPF_ORM_DataDict
+{
+ protected $keywords = array(
+ 'ADD', 'ALL', 'ALTER',
+ 'ANALYZE', 'AND', 'AS',
+ 'ASC', 'ASENSITIVE', 'BEFORE',
+ 'BETWEEN', 'BIGINT', 'BINARY',
+ 'BLOB', 'BOTH', 'BY', 'BIT',
+ 'CALL', 'CASCADE', 'CASE',
+ 'CHANGE', 'CHAR', 'CHARACTER',
+ 'CHECK', 'COLLATE', 'COLUMN',
+ 'CONDITION', 'CONNECTION', 'CONSTRAINT',
+ 'CONTINUE', 'CONVERT', 'CREATE',
+ 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR',
+ 'DATABASE', 'DATABASES', 'DAY_HOUR',
+ 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND',
+ 'DEC', 'DECIMAL', 'DECLARE',
+ 'DEFAULT', 'DELAYED', 'DELETE',
+ 'DESC', 'DESCRIBE', 'DETERMINISTIC',
+ 'DISTINCT', 'DISTINCTROW', 'DIV',
+ 'DOUBLE', 'DROP', 'DUAL',
+ 'EACH', 'ELSE', 'ELSEIF',
+ 'ENCLOSED', 'ESCAPED', 'EXISTS',
+ 'EXIT', 'EXPLAIN', 'FALSE',
+ 'FETCH', 'FLOAT', 'FLOAT4',
+ 'FLOAT8', 'FOR', 'FORCE',
+ 'FOREIGN', 'FROM', 'FULLTEXT',
+ 'GRANT', 'GROUP', 'HAVING',
+ 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE',
+ 'HOUR_SECOND', 'IF', 'IGNORE',
+ 'IN', 'INDEX', 'INFILE',
+ 'INNER', 'INOUT', 'INSENSITIVE',
+ 'INSERT', 'INT', 'INT1',
+ 'INT2', 'INT3', 'INT4',
+ 'INT8', 'INTEGER', 'INTERVAL',
+ 'INTO', 'IS', 'ITERATE',
+ 'JOIN', 'KEY', 'KEYS',
+ 'KILL', 'LEADING', 'LEAVE',
+ 'LEFT', 'LIKE', 'LIMIT',
+ 'LINES', 'LOAD', 'LOCALTIME',
+ 'LOCALTIMESTAMP', 'LOCK', 'LONG',
+ 'LONGBLOB', 'LONGTEXT', 'LOOP',
+ 'LOW_PRIORITY', 'MATCH', 'MEDIUMBLOB',
+ 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT',
+ 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD',
+ 'MODIFIES', 'NATURAL', 'NOT',
+ 'NO_WRITE_TO_BINLOG', 'NULL', 'NUMERIC',
+ 'ON', 'OPTIMIZE', 'OPTION',
+ 'OPTIONALLY', 'OR', 'ORDER',
+ 'OUT', 'OUTER', 'OUTFILE',
+ 'PRECISION', 'PRIMARY', 'PROCEDURE',
+ 'PURGE', 'RAID0', 'READ',
+ 'READS', 'REAL', 'REFERENCES',
+ 'REGEXP', 'RELEASE', 'RENAME',
+ 'REPEAT', 'REPLACE', 'REQUIRE',
+ 'RESTRICT', 'RETURN', 'REVOKE',
+ 'RIGHT', 'RLIKE', 'SCHEMA',
+ 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT',
+ 'SENSITIVE', 'SEPARATOR', 'SET',
+ 'SHOW', 'SMALLINT', 'SONAME',
+ 'SPATIAL', 'SPECIFIC', 'SQL',
+ 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING',
+ 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT',
+ 'SSL', 'STARTING', 'STRAIGHT_JOIN',
+ 'TABLE', 'TERMINATED', 'THEN',
+ 'TINYBLOB', 'TINYINT', 'TINYTEXT',
+ 'TO', 'TRAILING', 'TRIGGER',
+ 'TRUE', 'UNDO', 'UNION',
+ 'UNIQUE', 'UNLOCK', 'UNSIGNED',
+ 'UPDATE', 'USAGE', 'USE',
+ 'USING', 'UTC_DATE', 'UTC_TIME',
+ 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY',
+ 'VARCHAR', 'VARCHARACTER', 'VARYING',
+ 'WHEN', 'WHERE', 'WHILE',
+ 'WITH', 'WRITE', 'X509',
+ 'XOR', 'YEAR_MONTH', 'ZEROFILL'
+ );
+
+ public function getNativeDeclaration($field)
+ {
+ if ( ! isset($field['type'])) {
+ throw new IPF_ORM_DataDict_Exception('Missing column type.');
+ }
+
+ switch ($field['type']) {
+ case 'char':
+ $length = ( ! empty($field['length'])) ? $field['length'] : false;
+
+ return $length ? 'CHAR('.$length.')' : 'CHAR(255)';
+ case 'varchar':
+ case 'array':
+ case 'object':
+ case 'string':
+ case 'gzip':
+ if ( ! isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->conn->varchar_max_length;
+ } else {
+ $field['length'] = false;
+ }
+ }
+
+ $length = ($field['length'] <= $this->conn->varchar_max_length) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
+ : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
+ case 'clob':
+ if ( ! empty($field['length'])) {
+ $length = $field['length'];
+ if ($length <= 255) {
+ return 'TINYTEXT';
+ } elseif ($length <= 65532) {
+ return 'TEXT';
+ } elseif ($length <= 16777215) {
+ return 'MEDIUMTEXT';
+ }
+ }
+ return 'LONGTEXT';
+ case 'blob':
+ if ( ! empty($field['length'])) {
+ $length = $field['length'];
+ if ($length <= 255) {
+ return 'TINYBLOB';
+ } elseif ($length <= 65532) {
+ return 'BLOB';
+ } elseif ($length <= 16777215) {
+ return 'MEDIUMBLOB';
+ }
+ }
+ return 'LONGBLOB';
+ case 'enum':
+ if ($this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ $values = array();
+ foreach ($field['values'] as $value) {
+ $values[] = $this->conn->quote($value, 'varchar');
+ }
+ return 'ENUM('.implode(', ', $values).')';
+ }
+ // fall back to integer
+ case 'integer':
+ case 'int':
+ if ( ! empty($field['length'])) {
+ $length = $field['length'];
+ if ($length <= 1) {
+ return 'TINYINT';
+ } elseif ($length == 2) {
+ return 'SMALLINT';
+ } elseif ($length == 3) {
+ return 'MEDIUMINT';
+ } elseif ($length == 4) {
+ return 'INT';
+ } elseif ($length > 4) {
+ return 'BIGINT';
+ }
+ }
+ return 'INT';
+ case 'boolean':
+ return 'TINYINT(1)';
+ case 'date':
+ return 'DATE';
+ case 'time':
+ return 'TIME';
+ case 'timestamp':
+ return 'DATETIME';
+ case 'float':
+ case 'double':
+ return 'DOUBLE';
+ case 'decimal':
+ $length = !empty($field['length']) ? $field['length'] : 18;
+ $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(IPF_ORM::ATTR_DECIMAL_PLACES);
+ return 'DECIMAL('.$length.','.$scale.')';
+ case 'bit':
+ return 'BIT';
+ }
+ throw new IPF_ORM_Exception('Unknown field type \'' . $field['type'] . '\'.');
+ }
+
+ public function getPortableDeclaration(array $field)
+ {
+ $dbType = strtolower($field['type']);
+ $dbType = strtok($dbType, '(), ');
+ if ($dbType == 'national') {
+ $dbType = strtok('(), ');
+ }
+ if (isset($field['length'])) {
+ $length = $field['length'];
+ $decimal = '';
+ } else {
+ $length = strtok('(), ');
+ $decimal = strtok('(), ');
+ }
+ $type = array();
+ $unsigned = $fixed = null;
+
+ if ( ! isset($field['name'])) {
+ $field['name'] = '';
+ }
+
+ $values = null;
+
+ switch ($dbType) {
+ case 'tinyint':
+ $type[] = 'integer';
+ $type[] = 'boolean';
+ if (preg_match('/^(is|has)/', $field['name'])) {
+ $type = array_reverse($type);
+ }
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ $length = 1;
+ break;
+ case 'smallint':
+ $type[] = 'integer';
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ $length = 2;
+ break;
+ case 'mediumint':
+ $type[] = 'integer';
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ $length = 3;
+ break;
+ case 'int':
+ case 'integer':
+ $type[] = 'integer';
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ $length = 4;
+ break;
+ case 'bigint':
+ $type[] = 'integer';
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ $length = 8;
+ break;
+ case 'tinytext':
+ case 'mediumtext':
+ case 'longtext':
+ case 'text':
+ case 'text':
+ case 'varchar':
+ $fixed = false;
+ case 'string':
+ case 'char':
+ $type[] = 'string';
+ if ($length == '1') {
+ $type[] = 'boolean';
+ if (preg_match('/^(is|has)/', $field['name'])) {
+ $type = array_reverse($type);
+ }
+ } elseif (strstr($dbType, 'text')) {
+ $type[] = 'clob';
+ if ($decimal == 'binary') {
+ $type[] = 'blob';
+ }
+ }
+ if ($fixed !== false) {
+ $fixed = true;
+ }
+ break;
+ case 'enum':
+ $type[] = 'enum';
+ preg_match_all('/\'((?:\'\'|[^\'])*)\'/', $field['type'], $matches);
+ $length = 0;
+ $fixed = false;
+ if (is_array($matches)) {
+ foreach ($matches[1] as &$value) {
+ $value = str_replace('\'\'', '\'', $value);
+ $length = max($length, strlen($value));
+ }
+ if ($length == '1' && count($matches[1]) == 2) {
+ $type[] = 'boolean';
+ if (preg_match('/^(is|has)/', $field['name'])) {
+ $type = array_reverse($type);
+ }
+ }
+
+ $values = $matches[1];
+ }
+ $type[] = 'integer';
+ break;
+ case 'set':
+ $fixed = false;
+ $type[] = 'text';
+ $type[] = 'integer';
+ break;
+ case 'date':
+ $type[] = 'date';
+ $length = null;
+ break;
+ case 'datetime':
+ case 'timestamp':
+ $type[] = 'timestamp';
+ $length = null;
+ break;
+ case 'time':
+ $type[] = 'time';
+ $length = null;
+ break;
+ case 'float':
+ case 'double':
+ case 'real':
+ $type[] = 'float';
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ break;
+ case 'unknown':
+ case 'decimal':
+ case 'numeric':
+ $type[] = 'decimal';
+ $unsigned = preg_match('/ unsigned/i', $field['type']);
+ break;
+ case 'tinyblob':
+ case 'mediumblob':
+ case 'longblob':
+ case 'blob':
+ $type[] = 'blob';
+ $length = null;
+ break;
+ case 'year':
+ $type[] = 'integer';
+ $type[] = 'date';
+ $length = null;
+ break;
+ case 'bit':
+ $type[] = 'bit';
+ break;
+ default:
+ throw new IPF_ORM_Exception('unknown database attribute type: ' . $dbType);
+ }
+
+ $length = ((int) $length == 0) ? null : (int) $length;
+
+ if ($values === null) {
+ return array('type' => $type, 'length' => $length, 'unsigned' => $unsigned, 'fixed' => $fixed);
+ } else {
+ return array('type' => $type, 'length' => $length, 'unsigned' => $unsigned, 'fixed' => $fixed, 'values' => $values);
+ }
+ }
+
+ public function getCharsetFieldDeclaration($charset)
+ {
+ return 'CHARACTER SET ' . $charset;
+ }
+
+ public function getCollationFieldDeclaration($collation)
+ {
+ return 'COLLATE ' . $collation;
+ }
+
+ public function getIntegerDeclaration($name, $field)
+ {
+ $default = $autoinc = '';
+ if ( ! empty($field['autoincrement'])) {
+ $autoinc = ' AUTO_INCREMENT';
+ } elseif (array_key_exists('default', $field)) {
+ if ($field['default'] === '') {
+ $field['default'] = empty($field['notnull']) ? null : 0;
+ }
+ if (is_null($field['default'])) {
+ $default = ' DEFAULT NULL';
+ } else {
+ $default = ' DEFAULT '.$this->conn->quote($field['default']);
+ }
+ }
+
+ $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
+ $unsigned = (isset($field['unsigned']) && $field['unsigned']) ? ' UNSIGNED' : '';
+
+ $name = $this->conn->quoteIdentifier($name, true);
+
+ return $name . ' ' . $this->getNativeDeclaration($field) . $unsigned . $default . $notnull . $autoinc;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Event
+{
+ const CONN_QUERY = 1;
+ const CONN_EXEC = 2;
+ const CONN_PREPARE = 3;
+ const CONN_CONNECT = 4;
+ const CONN_CLOSE = 5;
+ const CONN_ERROR = 6;
+
+ const STMT_EXECUTE = 10;
+ const STMT_FETCH = 11;
+ const STMT_FETCHALL = 12;
+
+ const TX_BEGIN = 31;
+ const TX_COMMIT = 32;
+ const TX_ROLLBACK = 33;
+ const SAVEPOINT_CREATE = 34;
+ const SAVEPOINT_ROLLBACK = 35;
+ const SAVEPOINT_COMMIT = 36;
+
+ const HYDRATE = 40;
+
+ const RECORD_DELETE = 21;
+ const RECORD_SAVE = 22;
+ const RECORD_UPDATE = 23;
+ const RECORD_INSERT = 24;
+ const RECORD_SERIALIZE = 25;
+ const RECORD_UNSERIALIZE = 26;
+ const RECORD_DQL_SELECT = 28;
+ const RECORD_DQL_DELETE = 27;
+ const RECORD_DQL_UPDATE = 29;
+
+ protected $_invoker;
+
+ protected $_query;
+
+ protected $_params;
+
+ protected $_code;
+
+ protected $_startedMicrotime;
+
+ protected $_endedMicrotime;
+
+ protected $_options = array();
+
+ public function __construct($invoker, $code, $query = null, $params = array())
+ {
+ $this->_invoker = $invoker;
+ $this->_code = $code;
+ $this->_query = $query;
+ $this->_params = $params;
+ }
+
+ public function getQuery()
+ {
+ return $this->_query;
+ }
+
+ public function getName()
+ {
+ switch ($this->_code) {
+ case self::CONN_QUERY:
+ return 'query';
+ case self::CONN_EXEC:
+ return 'exec';
+ case self::CONN_PREPARE:
+ return 'prepare';
+ case self::CONN_CONNECT:
+ return 'connect';
+ case self::CONN_CLOSE:
+ return 'close';
+ case self::CONN_ERROR:
+ return 'error';
+
+ case self::STMT_EXECUTE:
+ return 'execute';
+ case self::STMT_FETCH:
+ return 'fetch';
+ case self::STMT_FETCHALL:
+ return 'fetch all';
+
+ case self::TX_BEGIN:
+ return 'begin';
+ case self::TX_COMMIT:
+ return 'commit';
+ case self::TX_ROLLBACK:
+ return 'rollback';
+
+ case self::SAVEPOINT_CREATE:
+ return 'create savepoint';
+ case self::SAVEPOINT_ROLLBACK:
+ return 'rollback savepoint';
+ case self::SAVEPOINT_COMMIT:
+ return 'commit savepoint';
+
+ case self::RECORD_DELETE:
+ return 'delete record';
+ case self::RECORD_SAVE:
+ return 'save record';
+ case self::RECORD_UPDATE:
+ return 'update record';
+ case self::RECORD_INSERT:
+ return 'insert record';
+ case self::RECORD_SERIALIZE:
+ return 'serialize record';
+ case self::RECORD_UNSERIALIZE:
+ return 'unserialize record';
+ case self::RECORD_DQL_SELECT:
+ return 'select records';
+ case self::RECORD_DQL_DELETE:
+ return 'delete records';
+ case self::RECORD_DQL_UPDATE:
+ return 'update records';
+ }
+ }
+
+ public function getCode()
+ {
+ return $this->_code;
+ }
+
+ public function __get($option)
+ {
+ if ( ! isset($this->_options[$option])) {
+ return null;
+ }
+
+ return $this->_options[$option];
+ }
+
+ public function skipOperation()
+ {
+ $this->_options['skipOperation'] = true;
+
+ return $this;
+ }
+
+ public function __set($option, $value)
+ {
+ $this->_options[$option] = $value;
+
+ return $this;
+ }
+
+ public function set($option, &$value)
+ {
+ $this->_options[$option] =& $value;
+
+ return $this;
+ }
+
+ public function start()
+ {
+ $this->_startedMicrotime = microtime(true);
+ }
+
+ public function hasEnded()
+ {
+ return ($this->_endedMicrotime != null);
+ }
+
+ public function end()
+ {
+ $this->_endedMicrotime = microtime(true);
+
+ return $this;
+ }
+
+ public function getInvoker()
+ {
+ return $this->_invoker;
+ }
+
+ public function setInvoker($invoker)
+ {
+ $this->_invoker = $invoker;
+ }
+
+ public function getParams()
+ {
+ return $this->_params;
+ }
+
+ public function getElapsedSecs()
+ {
+ if (is_null($this->_endedMicrotime)) {
+ return false;
+ }
+ return ($this->_endedMicrotime - $this->_startedMicrotime);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_EventListener implements IPF_ORM_EventListener_Interface
+{
+ public function preClose(IPF_ORM_Event $event){}
+ public function postClose(IPF_ORM_Event $event){}
+ public function onCollectionDelete(IPF_ORM_Collection $collection){}
+ public function onPreCollectionDelete(IPF_ORM_Collection $collection){}
+ public function onOpen(IPF_ORM_Connection $connection){}
+ public function preTransactionCommit(IPF_ORM_Event $event){}
+ public function postTransactionCommit(IPF_ORM_Event $event){}
+ public function preTransactionRollback(IPF_ORM_Event $event){}
+ public function postTransactionRollback(IPF_ORM_Event $event){}
+ public function preTransactionBegin(IPF_ORM_Event $event){}
+ public function postTransactionBegin(IPF_ORM_Event $event){}
+ public function preSavepointCommit(IPF_ORM_Event $event){}
+ public function postSavepointCommit(IPF_ORM_Event $event){}
+ public function preSavepointRollback(IPF_ORM_Event $event){}
+ public function postSavepointRollback(IPF_ORM_Event $event){}
+ public function preSavepointCreate(IPF_ORM_Event $event){}
+ public function postSavepointCreate(IPF_ORM_Event $event){}
+ public function postConnect(IPF_ORM_Event $event){}
+ public function preConnect(IPF_ORM_Event $event){}
+ public function preQuery(IPF_ORM_Event $event){}
+ public function postQuery(IPF_ORM_Event $event){}
+ public function prePrepare(IPF_ORM_Event $event){}
+ public function postPrepare(IPF_ORM_Event $event){}
+ public function preExec(IPF_ORM_Event $event){}
+ public function postExec(IPF_ORM_Event $event){}
+ public function preError(IPF_ORM_Event $event){}
+ public function postError(IPF_ORM_Event $event){}
+ public function preFetch(IPF_ORM_Event $event){}
+ public function postFetch(IPF_ORM_Event $event){}
+ public function preFetchAll(IPF_ORM_Event $event){}
+ public function postFetchAll(IPF_ORM_Event $event){}
+ public function preStmtExecute(IPF_ORM_Event $event){}
+ public function postStmtExecute(IPF_ORM_Event $event){}
+}
--- /dev/null
+<?php
+
+interface IPF_ORM_EventListener_Interface
+{
+ public function preTransactionCommit(IPF_ORM_Event $event);
+ public function postTransactionCommit(IPF_ORM_Event $event);
+ public function preTransactionRollback(IPF_ORM_Event $event);
+ public function postTransactionRollback(IPF_ORM_Event $event);
+ public function preTransactionBegin(IPF_ORM_Event $event);
+ public function postTransactionBegin(IPF_ORM_Event $event);
+ public function postConnect(IPF_ORM_Event $event);
+ public function preConnect(IPF_ORM_Event $event);
+ public function preQuery(IPF_ORM_Event $event);
+ public function postQuery(IPF_ORM_Event $event);
+ public function prePrepare(IPF_ORM_Event $event);
+ public function postPrepare(IPF_ORM_Event $event);
+ public function preExec(IPF_ORM_Event $event);
+ public function postExec(IPF_ORM_Event $event);
+ public function preError(IPF_ORM_Event $event);
+ public function postError(IPF_ORM_Event $event);
+ public function preFetch(IPF_ORM_Event $event);
+ public function postFetch(IPF_ORM_Event $event);
+ public function preFetchAll(IPF_ORM_Event $event);
+ public function postFetchAll(IPF_ORM_Event $event);
+ public function preStmtExecute(IPF_ORM_Event $event);
+ public function postStmtExecute(IPF_ORM_Event $event);
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Exception extends IPF_Exception
+{
+ protected static $_errorMessages = array(
+ IPF_ORM::ERR => 'unknown error',
+ IPF_ORM::ERR_ALREADY_EXISTS => 'already exists',
+ IPF_ORM::ERR_CANNOT_CREATE => 'can not create',
+ IPF_ORM::ERR_CANNOT_ALTER => 'can not alter',
+ IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace',
+ IPF_ORM::ERR_CANNOT_DELETE => 'can not delete',
+ IPF_ORM::ERR_CANNOT_DROP => 'can not drop',
+ IPF_ORM::ERR_CONSTRAINT => 'constraint violation',
+ IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+ IPF_ORM::ERR_DIVZERO => 'division by zero',
+ IPF_ORM::ERR_INVALID => 'invalid',
+ IPF_ORM::ERR_INVALID_DATE => 'invalid date or time',
+ IPF_ORM::ERR_INVALID_NUMBER => 'invalid number',
+ IPF_ORM::ERR_MISMATCH => 'mismatch',
+ IPF_ORM::ERR_NODBSELECTED => 'no database selected',
+ IPF_ORM::ERR_NOSUCHFIELD => 'no such field',
+ IPF_ORM::ERR_NOSUCHTABLE => 'no such table',
+ IPF_ORM::ERR_NOT_CAPABLE => 'IPF backend not capable',
+ IPF_ORM::ERR_NOT_FOUND => 'not found',
+ IPF_ORM::ERR_NOT_LOCKED => 'not locked',
+ IPF_ORM::ERR_SYNTAX => 'syntax error',
+ IPF_ORM::ERR_UNSUPPORTED => 'not supported',
+ IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
+ IPF_ORM::ERR_INVALID_DSN => 'invalid DSN',
+ IPF_ORM::ERR_CONNECT_FAILED => 'connect failed',
+ IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied',
+ IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
+ IPF_ORM::ERR_NOSUCHDB => 'no such database',
+ IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions',
+ IPF_ORM::ERR_LOADMODULE => 'error while including on demand module',
+ IPF_ORM::ERR_TRUNCATED => 'truncated',
+ IPF_ORM::ERR_DEADLOCK => 'deadlock detected',
+ );
+
+ public function errorMessage($value = null)
+ {
+ if (is_null($value)) {
+ return self::$_errorMessages;
+ }
+
+ return isset(self::$_errorMessages[$value]) ?
+ self::$_errorMessages[$value] : self::$_errorMessages[IPF_ORM::ERR];
+ }
+
+}
+
+
+
+
--- /dev/null
+<?php
+
+class IPF_ORM_Exception_Adapter extends IPF_ORM_Exception{}
--- /dev/null
+<?php
+
+class IPF_ORM_Exception_Connection extends IPF_ORM_Exception
+{
+ static protected $errorMessages = array(
+ IPF_ORM::ERR => 'unknown error',
+ IPF_ORM::ERR_ALREADY_EXISTS => 'already exists',
+ IPF_ORM::ERR_CANNOT_CREATE => 'can not create',
+ IPF_ORM::ERR_CANNOT_ALTER => 'can not alter',
+ IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace',
+ IPF_ORM::ERR_CANNOT_DELETE => 'can not delete',
+ IPF_ORM::ERR_CANNOT_DROP => 'can not drop',
+ IPF_ORM::ERR_CONSTRAINT => 'constraint violation',
+ IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+ IPF_ORM::ERR_DIVZERO => 'division by zero',
+ IPF_ORM::ERR_INVALID => 'invalid',
+ IPF_ORM::ERR_INVALID_DATE => 'invalid date or time',
+ IPF_ORM::ERR_INVALID_NUMBER => 'invalid number',
+ IPF_ORM::ERR_MISMATCH => 'mismatch',
+ IPF_ORM::ERR_NODBSELECTED => 'no database selected',
+ IPF_ORM::ERR_NOSUCHFIELD => 'no such field',
+ IPF_ORM::ERR_NOSUCHTABLE => 'no such table',
+ IPF_ORM::ERR_NOT_CAPABLE => 'IPF_ORM backend not capable',
+ IPF_ORM::ERR_NOT_FOUND => 'not found',
+ IPF_ORM::ERR_NOT_LOCKED => 'not locked',
+ IPF_ORM::ERR_SYNTAX => 'syntax error',
+ IPF_ORM::ERR_UNSUPPORTED => 'not supported',
+ IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
+ IPF_ORM::ERR_INVALID_DSN => 'invalid DSN',
+ IPF_ORM::ERR_CONNECT_FAILED => 'connect failed',
+ IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied',
+ IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
+ IPF_ORM::ERR_NOSUCHDB => 'no such database',
+ IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions',
+ IPF_ORM::ERR_LOADMODULE => 'error while including on demand module',
+ IPF_ORM::ERR_TRUNCATED => 'truncated',
+ IPF_ORM::ERR_DEADLOCK => 'deadlock detected',
+ );
+
+ protected $portableCode;
+
+ public function getPortableCode()
+ {
+ return $this->portableCode;
+ }
+
+ public function getPortableMessage()
+ {
+ return self::errorMessage($this->portableCode);
+ }
+
+ public function errorMessage($value = null)
+ {
+ return isset(self::$errorMessages[$value]) ?
+ self::$errorMessages[$value] : self::$errorMessages[IPF_ORM::ERR];
+ }
+
+ public function processErrorInfo(array $errorInfo)
+ { }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Exception_Locator extends IPF_ORM_Exception_Base{}
--- /dev/null
+<?php
+
+class IPF_ORM_Exception_Mysql extends IPF_ORM_Exception_Connection
+{
+ protected static $errorCodeMap = array(
+ 1004 => IPF_ORM::ERR_CANNOT_CREATE,
+ 1005 => IPF_ORM::ERR_CANNOT_CREATE,
+ 1006 => IPF_ORM::ERR_CANNOT_CREATE,
+ 1007 => IPF_ORM::ERR_ALREADY_EXISTS,
+ 1008 => IPF_ORM::ERR_CANNOT_DROP,
+ 1022 => IPF_ORM::ERR_ALREADY_EXISTS,
+ 1044 => IPF_ORM::ERR_ACCESS_VIOLATION,
+ 1046 => IPF_ORM::ERR_NODBSELECTED,
+ 1048 => IPF_ORM::ERR_CONSTRAINT,
+ 1049 => IPF_ORM::ERR_NOSUCHDB,
+ 1050 => IPF_ORM::ERR_ALREADY_EXISTS,
+ 1051 => IPF_ORM::ERR_NOSUCHTABLE,
+ 1054 => IPF_ORM::ERR_NOSUCHFIELD,
+ 1061 => IPF_ORM::ERR_ALREADY_EXISTS,
+ 1062 => IPF_ORM::ERR_ALREADY_EXISTS,
+ 1064 => IPF_ORM::ERR_SYNTAX,
+ 1091 => IPF_ORM::ERR_NOT_FOUND,
+ 1100 => IPF_ORM::ERR_NOT_LOCKED,
+ 1136 => IPF_ORM::ERR_VALUE_COUNT_ON_ROW,
+ 1142 => IPF_ORM::ERR_ACCESS_VIOLATION,
+ 1146 => IPF_ORM::ERR_NOSUCHTABLE,
+ 1216 => IPF_ORM::ERR_CONSTRAINT,
+ 1217 => IPF_ORM::ERR_CONSTRAINT,
+ 1451 => IPF_ORM::ERR_CONSTRAINT,
+ );
+ public function processErrorInfo(array $errorInfo)
+ {
+ $code = $errorInfo[1];
+ if (isset(self::$errorCodeMap[$code])) {
+ $this->portableCode = self::$errorCodeMap[$code];
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Exception extends IPF_Exception_Base{
+{
+ protected static $_errorMessages = array(
+ IPF_ORM::ERR => 'unknown error',
+ IPF_ORM::ERR_ALREADY_EXISTS => 'already exists',
+ IPF_ORM::ERR_CANNOT_CREATE => 'can not create',
+ IPF_ORM::ERR_CANNOT_ALTER => 'can not alter',
+ IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace',
+ IPF_ORM::ERR_CANNOT_DELETE => 'can not delete',
+ IPF_ORM::ERR_CANNOT_DROP => 'can not drop',
+ IPF_ORM::ERR_CONSTRAINT => 'constraint violation',
+ IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+ IPF_ORM::ERR_DIVZERO => 'division by zero',
+ IPF_ORM::ERR_INVALID => 'invalid',
+ IPF_ORM::ERR_INVALID_DATE => 'invalid date or time',
+ IPF_ORM::ERR_INVALID_NUMBER => 'invalid number',
+ IPF_ORM::ERR_MISMATCH => 'mismatch',
+ IPF_ORM::ERR_NODBSELECTED => 'no database selected',
+ IPF_ORM::ERR_NOSUCHFIELD => 'no such field',
+ IPF_ORM::ERR_NOSUCHTABLE => 'no such table',
+ IPF_ORM::ERR_NOT_CAPABLE => 'IPF backend not capable',
+ IPF_ORM::ERR_NOT_FOUND => 'not found',
+ IPF_ORM::ERR_NOT_LOCKED => 'not locked',
+ IPF_ORM::ERR_SYNTAX => 'syntax error',
+ IPF_ORM::ERR_UNSUPPORTED => 'not supported',
+ IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
+ IPF_ORM::ERR_INVALID_DSN => 'invalid DSN',
+ IPF_ORM::ERR_CONNECT_FAILED => 'connect failed',
+ IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied',
+ IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
+ IPF_ORM::ERR_NOSUCHDB => 'no such database',
+ IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions',
+ IPF_ORM::ERR_LOADMODULE => 'error while including on demand module',
+ IPF_ORM::ERR_TRUNCATED => 'truncated',
+ IPF_ORM::ERR_DEADLOCK => 'deadlock detected',
+ );
+
+ public function errorMessage($value = null)
+ {
+ if (is_null($value)) {
+ return self::$_errorMessages;
+ }
+
+ return isset(self::$_errorMessages[$value]) ?
+ self::$_errorMessages[$value] : self::$_errorMessages[IPF_ORM::ERR];
+ }
+
+}
+
+
+
+
--- /dev/null
+<?php
+
+class IPF_ORM_Exception_Validator extends IPF_ORM_Exception implements Countable, IteratorAggregate
+{
+ private $invalid = array();
+
+ public function __construct(array $invalid)
+ {
+ $this->invalid = $invalid;
+ parent::__construct($this->generateMessage());
+ }
+
+ public function getInvalidRecords()
+ {
+ return $this->invalid;
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->invalid);
+ }
+
+ public function count()
+ {
+ return count($this->invalid);
+ }
+
+ public function __toString()
+ {
+
+ return parent::__toString();
+ }
+
+ private function generateMessage()
+ {
+ $message = "";
+ foreach ($this->invalid as $record) {
+ $message .= "Validaton error in class " . get_class($record) . " ";
+ }
+ return $message;
+ }
+
+ public function inspect($function)
+ {
+ foreach ($this->invalid as $record) {
+ call_user_func($function, $record->getErrorStack());
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Export extends IPF_ORM_Connection_Module
+{
+ protected $valid_default_values = array(
+ 'text' => '',
+ 'boolean' => true,
+ 'integer' => 0,
+ 'decimal' => 0.0,
+ 'float' => 0.0,
+ 'timestamp' => '1970-01-01 00:00:00',
+ 'time' => '00:00:00',
+ 'date' => '1970-01-01',
+ 'clob' => '',
+ 'blob' => '',
+ 'string' => ''
+ );
+
+ public function dropDatabase($database)
+ {
+ $this->conn->execute($this->dropDatabaseSql($database));
+ }
+
+ public function dropDatabaseSql($database)
+ {
+ throw new IPF_ORM_Exception('Drop database not supported by this driver.');
+ }
+
+ public function dropTableSql($table)
+ {
+ return 'DROP TABLE ' . $this->conn->quoteIdentifier($table);
+ }
+
+ public function dropTable($table)
+ {
+ $this->conn->execute($this->dropTableSql($table));
+ }
+
+ public function dropIndex($table, $name)
+ {
+ return $this->conn->exec($this->dropIndexSql($table, $name));
+ }
+
+ public function dropIndexSql($table, $name)
+ {
+ $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
+
+ return 'DROP INDEX ' . $name;
+ }
+
+ public function dropConstraint($table, $name, $primary = false)
+ {
+ $table = $this->conn->quoteIdentifier($table);
+ $name = $this->conn->quoteIdentifier($name);
+
+ return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
+ }
+
+ public function dropForeignKey($table, $name)
+ {
+ return $this->dropConstraint($table, $name);
+ }
+
+ public function dropSequence($sequenceName)
+ {
+ $this->conn->exec($this->dropSequenceSql($sequenceName));
+ }
+
+ public function dropSequenceSql($sequenceName)
+ {
+ throw new IPF_ORM_Exception('Drop sequence not supported by this driver.');
+ }
+
+ public function createDatabase($database)
+ {
+ $this->conn->execute($this->createDatabaseSql($database));
+ }
+
+ public function createDatabaseSql($database)
+ {
+ throw new IPF_ORM_Exception('Create database not supported by this driver.');
+ }
+
+ public function createTableSql($name, array $fields, array $options = array())
+ {
+ if ( ! $name) {
+ throw new IPF_ORM_Exception('no valid table name specified');
+ }
+
+ if (empty($fields)) {
+ throw new IPF_ORM_Exception('no fields specified for table ' . $name);
+ }
+
+ $queryFields = $this->getFieldDeclarationList($fields);
+
+
+ if (isset($options['primary']) && ! empty($options['primary'])) {
+ $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')';
+ }
+
+ if (isset($options['indexes']) && ! empty($options['indexes'])) {
+ foreach($options['indexes'] as $index => $definition) {
+ $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
+ }
+ }
+
+ $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields;
+
+ $check = $this->getCheckDeclaration($fields);
+
+ if ( ! empty($check)) {
+ $query .= ', ' . $check;
+ }
+
+ $query .= ')';
+
+ $sql[] = $query;
+
+ if (isset($options['foreignKeys'])) {
+
+ foreach ((array) $options['foreignKeys'] as $k => $definition) {
+ if (is_array($definition)) {
+ $sql[] = $this->createForeignKeySql($name, $definition);
+ }
+ }
+ }
+ return $sql;
+ }
+
+ public function createTable($name, array $fields, array $options = array())
+ {
+ $sql = (array) $this->createTableSql($name, $fields, $options);
+
+ foreach ($sql as $query) {
+ $this->conn->execute($query);
+ }
+ }
+
+ public function createSequence($seqName, $start = 1, array $options = array())
+ {
+ return $this->conn->execute($this->createSequenceSql($seqName, $start = 1, $options));
+ }
+
+ public function createSequenceSql($seqName, $start = 1, array $options = array())
+ {
+ throw new IPF_ORM_Exception('Create sequence not supported by this driver.');
+ }
+
+ public function createConstraint($table, $name, $definition)
+ {
+ $sql = $this->createConstraintSql($table, $name, $definition);
+
+ return $this->conn->exec($sql);
+ }
+
+ public function createConstraintSql($table, $name, $definition)
+ {
+ $table = $this->conn->quoteIdentifier($table);
+ $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
+ $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
+
+ if (isset($definition['primary']) && $definition['primary']) {
+ $query .= ' PRIMARY KEY';
+ } elseif (isset($definition['unique']) && $definition['unique']) {
+ $query .= ' UNIQUE';
+ }
+
+ $fields = array();
+ foreach (array_keys($definition['fields']) as $field) {
+ $fields[] = $this->conn->quoteIdentifier($field, true);
+ }
+ $query .= ' ('. implode(', ', $fields) . ')';
+
+ return $query;
+ }
+
+ public function createIndex($table, $name, array $definition)
+ {
+ return $this->conn->execute($this->createIndexSql($table, $name, $definition));
+ }
+
+ public function createIndexSql($table, $name, array $definition)
+ {
+ $table = $this->conn->quoteIdentifier($table);
+ $name = $this->conn->quoteIdentifier($name);
+ $type = '';
+
+ if (isset($definition['type'])) {
+ switch (strtolower($definition['type'])) {
+ case 'unique':
+ $type = strtoupper($definition['type']) . ' ';
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+ }
+ }
+
+ $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
+
+ $fields = array();
+ foreach ($definition['fields'] as $field) {
+ $fields[] = $this->conn->quoteIdentifier($field);
+ }
+ $query .= ' (' . implode(', ', $fields) . ')';
+
+ return $query;
+ }
+
+ public function createForeignKeySql($table, array $definition)
+ {
+ $table = $this->conn->quoteIdentifier($table);
+
+ $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition);
+
+ return $query;
+ }
+
+ public function createForeignKey($table, array $definition)
+ {
+ $sql = $this->createForeignKeySql($table, $definition);
+
+ return $this->conn->execute($sql);
+ }
+
+ public function alterTable($name, array $changes, $check = false)
+ {
+ $sql = $this->alterTableSql($name, $changes, $check);
+
+ if (is_string($sql) && $sql) {
+ $this->conn->execute($sql);
+ }
+ }
+
+ public function alterTableSql($name, array $changes, $check = false)
+ {
+ throw new IPF_ORM_Exception('Alter table not supported by this driver.');
+ }
+
+ public function getFieldDeclarationList(array $fields)
+ {
+ foreach ($fields as $fieldName => $field) {
+ $query = $this->getDeclaration($fieldName, $field);
+
+ $queryFields[] = $query;
+ }
+ return implode(', ', $queryFields);
+ }
+
+ public function getDeclaration($name, array $field)
+ {
+
+ $default = $this->getDefaultFieldDeclaration($field);
+
+ $charset = (isset($field['charset']) && $field['charset']) ?
+ ' ' . $this->getCharsetFieldDeclaration($field['charset']) : '';
+
+ $collation = (isset($field['collation']) && $field['collation']) ?
+ ' ' . $this->getCollationFieldDeclaration($field['collation']) : '';
+
+ $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
+
+ $unique = (isset($field['unique']) && $field['unique']) ?
+ ' ' . $this->getUniqueFieldDeclaration() : '';
+
+ $check = (isset($field['check']) && $field['check']) ?
+ ' ' . $field['check'] : '';
+
+ $method = 'get' . $field['type'] . 'Declaration';
+
+ if (method_exists($this->conn->dataDict, $method)) {
+ return $this->conn->dataDict->$method($name, $field);
+ } else {
+ $dec = $this->conn->dataDict->getNativeDeclaration($field);
+ }
+ return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $unique . $check . $collation;
+ }
+
+ public function getDefaultFieldDeclaration($field)
+ {
+ $default = '';
+ if (isset($field['default'])) {
+ if ($field['default'] === '') {
+ $field['default'] = empty($field['notnull'])
+ ? null : $this->valid_default_values[$field['type']];
+
+ if ($field['default'] === '' &&
+ ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EMPTY_TO_NULL)) {
+ $field['default'] = null;
+ }
+ }
+
+ if ($field['type'] === 'boolean') {
+ $field['default'] = $this->conn->convertBooleans($field['default']);
+ }
+ $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
+ }
+ return $default;
+ }
+
+ public function getCheckDeclaration(array $definition)
+ {
+ $constraints = array();
+ foreach ($definition as $field => $def) {
+ if (is_string($def)) {
+ $constraints[] = 'CHECK (' . $def . ')';
+ } else {
+ if (isset($def['min'])) {
+ $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
+ }
+
+ if (isset($def['max'])) {
+ $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
+ }
+ }
+ }
+
+ return implode(', ', $constraints);
+ }
+
+ public function getIndexDeclaration($name, array $definition)
+ {
+ $name = $this->conn->quoteIdentifier($name);
+ $type = '';
+
+ if (isset($definition['type'])) {
+ if (strtolower($definition['type']) == 'unique') {
+ $type = strtoupper($definition['type']) . ' ';
+ } else {
+ throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+ }
+ }
+
+ if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) {
+ throw new IPF_ORM_Exception('No index columns given.');
+ }
+
+ $query = $type . 'INDEX ' . $name;
+
+ $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
+
+ return $query;
+ }
+
+ public function getIndexFieldDeclarationList(array $fields)
+ {
+ $ret = array();
+ foreach ($fields as $field => $definition) {
+ if (is_array($definition)) {
+ $ret[] = $this->conn->quoteIdentifier($field);
+ } else {
+ $ret[] = $this->conn->quoteIdentifier($definition);
+ }
+ }
+ return implode(', ', $ret);
+ }
+
+ public function getTemporaryTableQuery()
+ {
+ return 'TEMPORARY';
+ }
+
+ public function getForeignKeyDeclaration(array $definition)
+ {
+ $sql = $this->getForeignKeyBaseDeclaration($definition);
+ $sql .= $this->getAdvancedForeignKeyOptions($definition);
+
+ return $sql;
+ }
+
+ public function getAdvancedForeignKeyOptions(array $definition)
+ {
+ $query = '';
+ if ( ! empty($definition['onUpdate'])) {
+ $query .= ' ON UPDATE ' . $this->getForeignKeyRefentialAction($definition['onUpdate']);
+ }
+ if ( ! empty($definition['onDelete'])) {
+ $query .= ' ON DELETE ' . $this->getForeignKeyRefentialAction($definition['onDelete']);
+ }
+ return $query;
+ }
+
+ public function getForeignKeyReferentialAction($action)
+ {
+ $upper = strtoupper($action);
+ switch ($upper) {
+ case 'CASCADE':
+ case 'SET NULL':
+ case 'NO ACTION':
+ case 'RESTRICT':
+ case 'SET DEFAULT':
+ return $upper;
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown foreign key referential action \'' . $upper . '\' given.');
+ }
+ }
+
+ public function getForeignKeyBaseDeclaration(array $definition)
+ {
+ $sql = '';
+ if (isset($definition['name'])) {
+ $sql .= ' CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' ';
+ }
+ $sql .= 'FOREIGN KEY (';
+
+ if ( ! isset($definition['local'])) {
+ throw new IPF_ORM_Exception('Local reference field missing from definition.');
+ }
+ if ( ! isset($definition['foreign'])) {
+ throw new IPF_ORM_Exception('Foreign reference field missing from definition.');
+ }
+ if ( ! isset($definition['foreignTable'])) {
+ throw new IPF_ORM_Exception('Foreign reference table missing from definition.');
+ }
+
+ if ( ! is_array($definition['local'])) {
+ $definition['local'] = array($definition['local']);
+ }
+ if ( ! is_array($definition['foreign'])) {
+ $definition['foreign'] = array($definition['foreign']);
+ }
+
+ $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local']))
+ . ') REFERENCES '
+ . $this->conn->quoteIdentifier($definition['foreignTable']) . '('
+ . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')';
+
+ return $sql;
+ }
+
+ public function getUniqueFieldDeclaration()
+ {
+ return 'UNIQUE';
+ }
+
+ public function getCharsetFieldDeclaration($charset)
+ {
+ return '';
+ }
+
+ public function getCollationFieldDeclaration($collation)
+ {
+ return '';
+ }
+
+ public function exportSchema($directory = null)
+ {
+ if ($directory !== null) {
+ $models = IPF_ORM::filterInvalidModels(IPF_ORM::loadModels($directory));
+ } else {
+ $models = IPF_ORM::getLoadedModels();
+ }
+ $this->exportClasses($models);
+ }
+
+ public function exportSortedClassesSql($classes, $groupByConnection = true)
+ {
+ $connections = array();
+ foreach ($classes as $class) {
+ $connection = IPF_ORM_Manager::getInstance()->getConnectionForComponent($class);
+ $connectionName = $connection->getName();
+
+ if ( ! isset($connections[$connectionName])) {
+ $connections[$connectionName] = array(
+ 'create_tables' => array(),
+ 'create_sequences' => array(),
+ 'create_indexes' => array(),
+ 'alters' => array()
+ );
+ }
+
+ $sql = $connection->export->exportClassesSql(array($class));
+
+ // Build array of all the creates
+ // We need these to happen first
+ foreach ($sql as $key => $query) {
+ // If create table statement
+ if (substr($query, 0, strlen('CREATE TABLE')) == 'CREATE TABLE') {
+ $connections[$connectionName]['create_tables'][] = $query;
+
+ unset($sql[$key]);
+ continue;
+ }
+
+ // If create sequence statement
+ if (substr($query, 0, strlen('CREATE SEQUENCE')) == 'CREATE SEQUENCE') {
+ $connections[$connectionName]['create_sequences'][] = $query;
+
+ unset($sql[$key]);
+ continue;
+ }
+
+ // If create index statement
+ if (preg_grep("/CREATE .* INDEX/", array($query))) {
+ $connections[$connectionName]['create_indexes'][] = $query;
+
+ unset($sql[$key]);
+ continue;
+ }
+
+ // If alter table statement
+ if (substr($query, 0, strlen('ALTER TABLE')) == 'ALTER TABLE') {
+ $connections[$connectionName]['alters'][] = $query;
+
+ unset($sql[$key]);
+ continue;
+ }
+ }
+ }
+
+ // Loop over all the sql again to merge everything together so it is in the correct order
+ $build = array();
+ foreach ($connections as $connectionName => $sql) {
+ $build[$connectionName] = array_merge($sql['create_tables'], $sql['create_sequences'], $sql['create_indexes'], $sql['alters']);
+ }
+
+ if ( ! $groupByConnection) {
+ $new = array();
+ foreach($build as $connectionname => $sql) {
+ $new = array_merge($new, $sql);
+ }
+ $build = $new;
+ }
+ return $build;
+ }
+
+ public function exportClasses(array $classes)
+ {
+ $queries = $this->exportSortedClassesSql($classes);
+
+ foreach ($queries as $connectionName => $sql) {
+ $connection = IPF_ORM_Manager::getInstance()->getConnection($connectionName);
+
+ $connection->beginTransaction();
+
+ foreach ($sql as $query) {
+ try {
+ $connection->exec($query);
+ } catch (IPF_ORM_Exception $e) {
+ // we only want to silence table already exists errors
+ if ($e->getPortableCode() !== IPF_ORM::ERR_ALREADY_EXISTS) {
+ $connection->rollback();
+ throw new IPF_ORM_Exception($e->getMessage() . '. Failing Query: ' . $query);
+ }
+ }
+ }
+ $connection->commit();
+ }
+ }
+
+ public function exportClassesSql(array $classes)
+ {
+ $models = IPF_ORM::filterInvalidModels($classes);
+
+ $sql = array();
+
+ foreach ($models as $name) {
+ $record = new $name();
+ $table = $record->getTable();
+
+ $parents = $table->getOption('joinedParents');
+
+ foreach ($parents as $parent) {
+ $data = $table->getConnection()->getTable($parent)->getExportableFormat();
+
+ $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
+
+ $sql = array_merge($sql, (array) $query);
+ }
+
+ $data = $table->getExportableFormat();
+
+ $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
+
+ if (is_array($query)) {
+ $sql = array_merge($sql, $query);
+ } else {
+ $sql[] = $query;
+ }
+
+ if ($table->getAttribute(IPF_ORM::ATTR_EXPORT) & IPF_ORM::EXPORT_PLUGINS) {
+ $sql = array_merge($sql, $this->exportGeneratorsSql($table));
+ }
+ }
+
+ $sql = array_unique($sql);
+
+ rsort($sql);
+
+ return $sql;
+ }
+
+ public function getAllGenerators(IPF_ORM_Table $table)
+ {
+ $generators = array();
+
+ foreach ($table->getGenerators() as $name => $generator) {
+ if ($generator === null) {
+ continue;
+ }
+
+ $generators[] = $generator;
+
+ $generatorTable = $generator->getTable();
+
+ if ($generatorTable instanceof IPF_ORM_Table) {
+ $generators = array_merge($generators, $this->getAllGenerators($generatorTable));
+ }
+ }
+
+ return $generators;
+ }
+
+ public function exportGeneratorsSql(IPF_ORM_Table $table)
+ {
+ $sql = array();
+
+ foreach ($this->getAllGenerators($table) as $name => $generator) {
+ $table = $generator->getTable();
+
+ // Make sure plugin has a valid table
+ if ($table instanceof IPF_ORM_Table) {
+ $data = $table->getExportableFormat();
+
+ $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
+
+ $sql = array_merge($sql, (array) $query);
+ }
+ }
+
+ return $sql;
+ }
+
+ public function exportSql($directory = null)
+ {
+ if ($directory !== null) {
+ $models = IPF_ORM::filterInvalidModels(IPF_ORM::loadModels($directory));
+ } else {
+ $models = IPF_ORM::getLoadedModels();
+ }
+
+ return $this->exportSortedClassesSql($models, false);
+ }
+
+ public function exportTable(IPF_ORM_Table $table)
+ {
+ try {
+ $data = $table->getExportableFormat();
+
+ $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']);
+ } catch(IPF_ORM_Exception $e) {
+ // we only want to silence table already exists errors
+ if ($e->getPortableCode() !== IPF_ORM::ERR_ALREADY_EXISTS) {
+ throw $e;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Export_Mysql extends IPF_ORM_Export
+{
+ public function createDatabaseSql($name)
+ {
+ return 'CREATE DATABASE ' . $this->conn->quoteIdentifier($name, true);
+ }
+
+ public function dropDatabaseSql($name)
+ {
+ return 'DROP DATABASE ' . $this->conn->quoteIdentifier($name);
+ }
+
+ public function createTableSql($name, array $fields, array $options = array())
+ {
+ if ( ! $name)
+ throw new IPF_ORM_Exception('no valid table name specified');
+
+ if (empty($fields)) {
+ throw new IPF_ORM_Exception('no fields specified for table "'.$name.'"');
+ }
+ $queryFields = $this->getFieldDeclarationList($fields);
+
+ // build indexes for all foreign key fields (needed in MySQL!!)
+ if (isset($options['foreignKeys'])) {
+ foreach ($options['foreignKeys'] as $fk) {
+ $local = $fk['local'];
+ $found = false;
+ if (isset($options['indexes'])) {
+ foreach ($options['indexes'] as $definition) {
+ if (is_string($definition['fields'])) {
+ // Check if index already exists on the column
+ $found = ($local == $definition['fields']);
+ } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) {
+ // Index already exists on the column
+ $found = true;
+ }
+ }
+ }
+ if (isset($options['primary']) && !empty($options['primary']) &&
+ in_array($local, $options['primary'])) {
+ // field is part of the PK and therefore already indexed
+ $found = true;
+ }
+
+ if ( ! $found) {
+ if (is_array($local)) {
+ foreach($local as $localidx) {
+ $options['indexes'][$localidx] = array('fields' => array($localidx => array()));
+ }
+ } else {
+ $options['indexes'][$local] = array('fields' => array($local => array()));
+ }
+ }
+ }
+ }
+
+ // add all indexes
+ if (isset($options['indexes']) && ! empty($options['indexes'])) {
+ foreach($options['indexes'] as $index => $definition) {
+ $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
+ }
+ }
+
+ // attach all primary keys
+ if (isset($options['primary']) && ! empty($options['primary'])) {
+ $keyColumns = array_values($options['primary']);
+ $keyColumns = array_map(array($this->conn, 'quoteIdentifier'), $keyColumns);
+ $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
+ }
+
+ $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields . ')';
+
+ $optionStrings = array();
+
+ if (isset($options['comment'])) {
+ $optionStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'text');
+ }
+ if (isset($options['charset'])) {
+ $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
+ }
+ if (isset($options['collate'])) {
+ $optionStrings['collate'] = 'COLLATE ' . $options['collate'];
+ }
+
+ $type = false;
+
+ // get the type of the table
+ if (isset($options['type'])) {
+ $type = $options['type'];
+ } else {
+ $type = $this->conn->getAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE);
+ }
+
+ if ($type) {
+ $optionStrings[] = 'ENGINE = ' . $type;
+ }
+
+ if ( ! empty($optionStrings)) {
+ $query.= ' '.implode(' ', $optionStrings);
+ }
+ $sql[] = $query;
+
+ if (isset($options['foreignKeys'])) {
+
+ foreach ((array) $options['foreignKeys'] as $k => $definition) {
+ if (is_array($definition)) {
+ $sql[] = $this->createForeignKeySql($name, $definition);
+ }
+ }
+ }
+ return $sql;
+ }
+
+ public function getDeclaration($name, array $field)
+ {
+
+ $default = $this->getDefaultFieldDeclaration($field);
+
+ $charset = (isset($field['charset']) && $field['charset']) ?
+ ' ' . $this->getCharsetFieldDeclaration($field['charset']) : '';
+
+ $collation = (isset($field['collation']) && $field['collation']) ?
+ ' ' . $this->getCollationFieldDeclaration($field['collation']) : '';
+
+ $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
+
+ $unique = (isset($field['unique']) && $field['unique']) ?
+ ' ' . $this->getUniqueFieldDeclaration() : '';
+
+ $check = (isset($field['check']) && $field['check']) ?
+ ' ' . $field['check'] : '';
+
+ $comment = (isset($field['comment']) && $field['comment']) ?
+ " COMMENT '" . $field['comment'] . "'" : '';
+
+ $method = 'get' . $field['type'] . 'Declaration';
+
+ if (method_exists($this->conn->dataDict, $method)) {
+ return $this->conn->dataDict->$method($name, $field);
+ } else {
+ $dec = $this->conn->dataDict->getNativeDeclaration($field);
+ }
+ return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $comment . $unique . $check . $collation;
+ }
+
+ public function alterTableSql($name, array $changes, $check = false)
+ {
+ if ( ! $name) {
+ throw new IPF_ORM_Exception('no valid table name specified');
+ }
+ foreach ($changes as $changeName => $change) {
+ switch ($changeName) {
+ case 'add':
+ case 'remove':
+ case 'change':
+ case 'rename':
+ case 'name':
+ break;
+ default:
+ throw new IPF_ORM_Exception('change type "' . $changeName . '" not yet supported');
+ }
+ }
+
+ if ($check) {
+ return true;
+ }
+
+ $query = '';
+ if ( ! empty($changes['name'])) {
+ $change_name = $this->conn->quoteIdentifier($changes['name']);
+ $query .= 'RENAME TO ' . $change_name;
+ }
+
+ if ( ! empty($changes['add']) && is_array($changes['add'])) {
+ foreach ($changes['add'] as $fieldName => $field) {
+ if ($query) {
+ $query.= ', ';
+ }
+ $query.= 'ADD ' . $this->getDeclaration($fieldName, $field);
+ }
+ }
+
+ if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
+ foreach ($changes['remove'] as $fieldName => $field) {
+ if ($query) {
+ $query .= ', ';
+ }
+ $fieldName = $this->conn->quoteIdentifier($fieldName);
+ $query .= 'DROP ' . $fieldName;
+ }
+ }
+
+ $rename = array();
+ if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
+ foreach ($changes['rename'] as $fieldName => $field) {
+ $rename[$field['name']] = $fieldName;
+ }
+ }
+
+ if ( ! empty($changes['change']) && is_array($changes['change'])) {
+ foreach ($changes['change'] as $fieldName => $field) {
+ if ($query) {
+ $query.= ', ';
+ }
+ if (isset($rename[$fieldName])) {
+ $oldFieldName = $rename[$fieldName];
+ unset($rename[$fieldName]);
+ } else {
+ $oldFieldName = $fieldName;
+ }
+ $oldFieldName = $this->conn->quoteIdentifier($oldFieldName, true);
+ $query .= 'CHANGE ' . $oldFieldName . ' '
+ . $this->getDeclaration($fieldName, $field['definition']);
+ }
+ }
+
+ if ( ! empty($rename) && is_array($rename)) {
+ foreach ($rename as $renameName => $renamedField) {
+ if ($query) {
+ $query.= ', ';
+ }
+ $field = $changes['rename'][$renamedField];
+ $renamedField = $this->conn->quoteIdentifier($renamedField, true);
+ $query .= 'CHANGE ' . $renamedField . ' '
+ . $this->getDeclaration($field['name'], $field['definition']);
+ }
+ }
+
+ if ( ! $query) {
+ return false;
+ }
+
+ $name = $this->conn->quoteIdentifier($name, true);
+
+ return 'ALTER TABLE ' . $name . ' ' . $query;
+ }
+
+ public function createSequence($sequenceName, $start = 1, array $options = array())
+ {
+ $sequenceName = $this->conn->quoteIdentifier($sequenceName, true);
+ $seqcolName = $this->conn->quoteIdentifier($this->conn->getAttribute(IPF_ORM::ATTR_SEQCOL_NAME), true);
+
+ $optionsStrings = array();
+
+ if (isset($options['comment']) && ! empty($options['comment'])) {
+ $optionsStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'string');
+ }
+
+ if (isset($options['charset']) && ! empty($options['charset'])) {
+ $optionsStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
+
+ if (isset($options['collate'])) {
+ $optionsStrings['charset'] .= ' COLLATE ' . $options['collate'];
+ }
+ }
+
+ $type = false;
+
+ if (isset($options['type'])) {
+ $type = $options['type'];
+ } else {
+ $type = $this->conn->getAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE);
+ }
+ if ($type) {
+ $optionsStrings[] = 'ENGINE = ' . $type;
+ }
+
+
+ try {
+ $query = 'CREATE TABLE ' . $sequenceName
+ . ' (' . $seqcolName . ' BIGINT NOT NULL AUTO_INCREMENT, PRIMARY KEY ('
+ . $seqcolName . ')) ' . implode($optionsStrings, ' ');
+
+ $res = $this->conn->exec($query);
+ } catch(IPF_ORM_Exception $e) {
+ throw new IPF_ORM_Exception('could not create sequence table');
+ }
+
+ if ($start == 1 && $res == 1)
+ return true;
+
+ $query = 'INSERT INTO ' . $sequenceName
+ . ' (' . $seqcolName . ') VALUES (' . ($start - 1) . ')';
+
+ $res = $this->conn->exec($query);
+
+ if ($res == 1)
+ return true;
+
+ // Handle error
+ try {
+ $result = $this->conn->exec('DROP TABLE ' . $sequenceName);
+ } catch(IPF_ORM_Exception $e) {
+ throw new IPF_ORM_Exception('could not drop inconsistent sequence table');
+ }
+
+
+ }
+
+ public function createIndexSql($table, $name, array $definition)
+ {
+ $table = $table;
+ $name = $this->conn->formatter->getIndexName($name);
+ $name = $this->conn->quoteIdentifier($name);
+ $type = '';
+ if (isset($definition['type'])) {
+ switch (strtolower($definition['type'])) {
+ case 'fulltext':
+ case 'unique':
+ $type = strtoupper($definition['type']) . ' ';
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+ }
+ }
+ $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
+ $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
+
+ return $query;
+ }
+
+ public function getDefaultFieldDeclaration($field)
+ {
+ $default = '';
+ if (isset($field['default']) && ( ! isset($field['length']) || $field['length'] <= 255)) {
+ if ($field['default'] === '') {
+ $field['default'] = empty($field['notnull'])
+ ? null : $this->valid_default_values[$field['type']];
+
+ if ($field['default'] === ''
+ && ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EMPTY_TO_NULL)
+ ) {
+ $field['default'] = ' ';
+ }
+ }
+
+ // Proposed patch:
+ if ($field['type'] == 'enum' && $this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ $fieldType = 'varchar';
+ } else {
+ $fieldType = $field['type'];
+ }
+
+ $default = ' DEFAULT ' . $this->conn->quote($field['default'], $fieldType);
+ //$default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
+ }
+ return $default;
+ }
+
+ public function getIndexDeclaration($name, array $definition)
+ {
+ $name = $this->conn->formatter->getIndexName($name);
+ $type = '';
+ if (isset($definition['type'])) {
+ switch (strtolower($definition['type'])) {
+ case 'fulltext':
+ case 'unique':
+ $type = strtoupper($definition['type']) . ' ';
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+ }
+ }
+
+ if ( ! isset($definition['fields'])) {
+ throw new IPF_ORM_Exception('No index columns given.');
+ }
+ if ( ! is_array($definition['fields'])) {
+ $definition['fields'] = array($definition['fields']);
+ }
+
+ $query = $type . 'INDEX ' . $this->conn->quoteIdentifier($name);
+
+ $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
+
+ return $query;
+ }
+
+ public function getIndexFieldDeclarationList(array $fields)
+ {
+ $declFields = array();
+
+ foreach ($fields as $fieldName => $field) {
+ $fieldString = $this->conn->quoteIdentifier($fieldName);
+
+ if (is_array($field)) {
+ if (isset($field['length'])) {
+ $fieldString .= '(' . $field['length'] . ')';
+ }
+
+ if (isset($field['sorting'])) {
+ $sort = strtoupper($field['sorting']);
+ switch ($sort) {
+ case 'ASC':
+ case 'DESC':
+ $fieldString .= ' ' . $sort;
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown index sorting option given.');
+ }
+ }
+ } else {
+ $fieldString = $this->conn->quoteIdentifier($field);
+ }
+ $declFields[] = $fieldString;
+ }
+ return implode(', ', $declFields);
+ }
+
+ public function getAdvancedForeignKeyOptions(array $definition)
+ {
+ $query = '';
+ if ( ! empty($definition['match'])) {
+ $query .= ' MATCH ' . $definition['match'];
+ }
+ if ( ! empty($definition['onUpdate'])) {
+ $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']);
+ }
+ if ( ! empty($definition['onDelete'])) {
+ $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']);
+ }
+ return $query;
+ }
+
+ public function dropIndexSql($table, $name)
+ {
+ $table = $this->conn->quoteIdentifier($table, true);
+ $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name), true);
+ return 'DROP INDEX ' . $name . ' ON ' . $table;
+ }
+
+ public function dropTableSql($table)
+ {
+ $table = $this->conn->quoteIdentifier($table, true);
+ return 'DROP TABLE ' . $table;
+ }
+
+ public function dropForeignKey($table, $name)
+ {
+ $table = $this->conn->quoteIdentifier($table);
+ $name = $this->conn->quoteIdentifier($name);
+
+ return $this->conn->exec('ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $name);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Formatter extends IPF_ORM_Connection_Module
+{
+ public function escapePattern($text)
+ {
+ if ( ! $this->string_quoting['escape_pattern']) {
+ return $text;
+ }
+ $tmp = $this->conn->string_quoting;
+
+ $text = str_replace($tmp['escape_pattern'],
+ $tmp['escape_pattern'] .
+ $tmp['escape_pattern'], $text);
+
+ foreach ($this->wildcards as $wildcard) {
+ $text = str_replace($wildcard, $tmp['escape_pattern'] . $wildcard, $text);
+ }
+ return $text;
+ }
+
+ public function convertBooleans($item)
+ {
+ if (is_array($item)) {
+ foreach ($item as $k => $value) {
+ if (is_bool($value)) {
+ $item[$k] = (int) $value;
+ }
+ }
+ } else {
+ if (is_bool($item)) {
+ $item = (int) $item;
+ }
+ }
+ return $item;
+ }
+
+ public function quoteIdentifier($str, $checkOption = true)
+ {
+ if ($checkOption && ! $this->conn->getAttribute(IPF_ORM::ATTR_QUOTE_IDENTIFIER)) {
+ return $str;
+ }
+ $tmp = $this->conn->identifier_quoting;
+ $str = str_replace($tmp['end'],
+ $tmp['escape'] .
+ $tmp['end'], $str);
+
+ return $tmp['start'] . $str . $tmp['end'];
+ }
+
+ public function quoteMultipleIdentifier($arr, $checkOption = true)
+ {
+ foreach ($arr as $k => $v) {
+ $arr[$k] = $this->quoteIdentifier($v, $checkOption);
+ }
+
+ return $arr;
+ }
+
+ public function quote($input, $type = null)
+ {
+ if ($type == null) {
+ $type = gettype($input);
+ }
+ switch ($type) {
+ case 'integer':
+ case 'enum':
+ case 'boolean':
+ case 'double':
+ case 'float':
+ case 'bool':
+ case 'decimal':
+ case 'int':
+ return $input;
+ case 'array':
+ case 'object':
+ $input = serialize($input);
+ case 'date':
+ case 'time':
+ case 'timestamp':
+ case 'string':
+ case 'char':
+ case 'varchar':
+ case 'text':
+ case 'gzip':
+ case 'blob':
+ case 'clob':
+ $this->conn->connect();
+
+ return $this->conn->getDbh()->quote($input);
+ }
+ }
+
+ public function fixSequenceName($sqn)
+ {
+ $seqPattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $this->conn->getAttribute(IPF_ORM::ATTR_SEQNAME_FORMAT)).'$/i';
+ $seqName = preg_replace($seqPattern, '\\1', $sqn);
+
+ if ($seqName && ! strcasecmp($sqn, $this->getSequenceName($seqName))) {
+ return $seqName;
+ }
+ return $sqn;
+ }
+
+ public function fixIndexName($idx)
+ {
+ $indexPattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $this->conn->getAttribute(IPF_ORM::ATTR_IDXNAME_FORMAT)).'$/i';
+ $indexName = preg_replace($indexPattern, '\\1', $idx);
+ if ($indexName && ! strcasecmp($idx, $this->getIndexName($indexName))) {
+ return $indexName;
+ }
+ return $idx;
+ }
+
+ public function getSequenceName($sqn)
+ {
+ return sprintf($this->conn->getAttribute(IPF_ORM::ATTR_SEQNAME_FORMAT),
+ preg_replace('/[^a-z0-9_\$.]/i', '_', $sqn));
+ }
+
+ public function getIndexName($idx)
+ {
+ return sprintf($this->conn->getAttribute(IPF_ORM::ATTR_IDXNAME_FORMAT),
+ preg_replace('/[^a-z0-9_\$]/i', '_', $idx));
+ }
+
+ public function getTableName($table)
+ {
+ return sprintf($this->conn->getAttribute(IPF_ORM::ATTR_TBLNAME_FORMAT),
+ $table);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Hydrator extends IPF_ORM_Hydrator_Abstract
+{
+ public function hydrateResultSet($stmt, $tableAliases)
+ {
+ $hydrationMode = $this->_hydrationMode;
+
+ $this->_tableAliases = $tableAliases;
+
+ if ($hydrationMode == IPF_ORM::HYDRATE_NONE) {
+ return $stmt->fetchAll(PDO::FETCH_NUM);
+ }
+
+ if ($hydrationMode == IPF_ORM::HYDRATE_ARRAY) {
+ $driver = new IPF_ORM_Hydrator_ArrayDriver();
+ } else {
+ $driver = new IPF_ORM_Hydrator_RecordDriver();
+ }
+
+ // Used variables during hydration
+ reset($this->_queryComponents);
+ $rootAlias = key($this->_queryComponents);
+ $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName();
+ // if only one component is involved we can make our lives easier
+ $isSimpleQuery = count($this->_queryComponents) <= 1;
+ // Holds the resulting hydrated data structure
+ $result = array();
+ // Holds hydration listeners that get called during hydration
+ $listeners = array();
+ // Lookup map to quickly discover/lookup existing records in the result
+ $identifierMap = array();
+ // Holds for each component the last previously seen element in the result set
+ $prev = array();
+ // holds the values of the identifier/primary key fields of components,
+ // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever)
+ // the $idTemplate is a prepared template. $id is set to a fresh template when
+ // starting to process a row.
+ $id = array();
+ $idTemplate = array();
+
+ $result = $driver->getElementCollection($rootComponentName);
+
+ if ($stmt === false || $stmt === 0) {
+ return $result;
+ }
+
+ // Initialize
+ foreach ($this->_queryComponents as $dqlAlias => $data) {
+ $componentName = $data['table']->getComponentName();
+ $listeners[$componentName] = $data['table']->getRecordListener();
+ $identifierMap[$dqlAlias] = array();
+ $prev[$dqlAlias] = null;
+ $idTemplate[$dqlAlias] = '';
+ }
+
+ $event = new IPF_ORM_Event(null, IPF_ORM_Event::HYDRATE, null);
+
+ // Process result set
+ $cache = array();
+ while ($data = $stmt->fetch(IPF_ORM::FETCH_ASSOC)) {
+ $id = $idTemplate; // initialize the id-memory
+ $nonemptyComponents = array();
+ $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
+
+ //
+ // hydrate the data of the root component from the current row
+ //
+ $table = $this->_queryComponents[$rootAlias]['table'];
+ $componentName = $table->getComponentName();
+ // Ticket #1115 (getInvoker() should return the component that has addEventListener)
+ $event->setInvoker($table);
+ $event->set('data', $rowData[$rootAlias]);
+ $listeners[$componentName]->preHydrate($event);
+
+ $index = false;
+
+ // Check for an existing element
+ if ($isSimpleQuery || ! isset($identifierMap[$rootAlias][$id[$rootAlias]])) {
+ $element = $driver->getElement($rowData[$rootAlias], $componentName);
+ $event->set('data', $element);
+ $listeners[$componentName]->postHydrate($event);
+
+ // do we need to index by a custom field?
+ if ($field = $this->_getCustomIndexField($rootAlias)) {
+ if (isset($result[$field])) {
+ throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping.");
+ } else if ( ! isset($element[$field])) {
+ throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key.");
+ }
+ $result[$element[$field]] = $element;
+ } else {
+ $result[] = $element;
+ }
+
+ $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result);
+ } else {
+ $index = $identifierMap[$rootAlias][$id[$rootAlias]];
+ }
+
+ $this->_setLastElement($prev, $result, $index, $rootAlias, false);
+ unset($rowData[$rootAlias]);
+
+ // end hydrate data of the root component for the current row
+
+
+ // $prev[$rootAlias] now points to the last element in $result.
+ // now hydrate the rest of the data found in the current row, that belongs to other
+ // (related) components.
+ foreach ($rowData as $dqlAlias => $data) {
+ $index = false;
+ $map = $this->_queryComponents[$dqlAlias];
+ $table = $map['table'];
+ $componentName = $table->getComponentName();
+ $event->set('data', $data);
+ $listeners[$componentName]->preHydrate($event);
+
+ $parent = $map['parent'];
+ $relation = $map['relation'];
+ $relationAlias = $map['relation']->getAlias();
+
+ $path = $parent . '.' . $dqlAlias;
+
+ if ( ! isset($prev[$parent])) {
+ unset($prev[$dqlAlias]); // Ticket #1228
+ continue;
+ }
+
+ // check the type of the relation
+ if ( ! $relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) {
+ $oneToOne = false;
+ // append element
+ if (isset($nonemptyComponents[$dqlAlias])) {
+ $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
+ $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
+ $indexIsValid = $index !== false ? isset($prev[$parent][$relationAlias][$index]) : false;
+ if ( ! $indexExists || ! $indexIsValid) {
+ $element = $driver->getElement($data, $componentName);
+ $event->set('data', $element);
+ $listeners[$componentName]->postHydrate($event);
+
+ if ($field = $this->_getCustomIndexField($dqlAlias)) {
+ if (isset($prev[$parent][$relationAlias][$element[$field]])) {
+ throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping.");
+ } else if ( ! isset($element[$field])) {
+ throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key.");
+ }
+ $prev[$parent][$relationAlias][$element[$field]] = $element;
+ } else {
+ $prev[$parent][$relationAlias][] = $element;
+ }
+ $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]);
+ }
+ // register collection for later snapshots
+ $driver->registerCollection($prev[$parent][$relationAlias]);
+ }
+ } else {
+ // 1-1 relation
+ $oneToOne = true;
+
+ if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($prev[$parent][$relationAlias])) {
+ $prev[$parent][$relationAlias] = $driver->getNullPointer();
+ } else if ( ! isset($prev[$parent][$relationAlias])) {
+ $element = $driver->getElement($data, $componentName);
+
+ // [FIX] Tickets #1205 and #1237
+ $event->set('data', $element);
+ $listeners[$componentName]->postHydrate($event);
+
+ $prev[$parent][$relationAlias] = $element;
+ }
+ }
+
+ $coll =& $prev[$parent][$relationAlias];
+ $this->_setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne);
+ }
+ }
+
+ $stmt->closeCursor();
+ $driver->flush();
+ //$e = microtime(true);
+ //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records<br />';
+
+ return $result;
+ }
+
+ protected function _setLastElement(&$prev, &$coll, $index, $dqlAlias, $oneToOne)
+ {
+ if ($coll === self::$_null || $coll === null) {
+ unset($prev[$dqlAlias]); // Ticket #1228
+ return;
+ }
+
+ if ($index !== false) {
+ // Link element at $index to previous element for the component
+ // identified by the DQL alias $alias
+ $prev[$dqlAlias] =& $coll[$index];
+ return;
+ }
+
+ if (is_array($coll) && $coll) {
+ if ($oneToOne) {
+ $prev[$dqlAlias] =& $coll;
+ } else {
+ end($coll);
+ $prev[$dqlAlias] =& $coll[key($coll)];
+ }
+ } else if (count($coll) > 0) {
+ $prev[$dqlAlias] = $coll->getLast();
+ }
+ }
+
+ protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents)
+ {
+ $rowData = array();
+
+ foreach ($data as $key => $value) {
+ // Parse each column name only once. Cache the results.
+ if ( ! isset($cache[$key])) {
+ // check ignored names. fastest solution for now. if we get more we'll start
+ // to introduce a list.
+ if ($key == 'IPF_ORM_ROWNUM') continue;
+ $e = explode('__', $key);
+ $last = strtolower(array_pop($e));
+ $cache[$key]['dqlAlias'] = $this->_tableAliases[strtolower(implode('__', $e))];
+ $table = $this->_queryComponents[$cache[$key]['dqlAlias']]['table'];
+ $fieldName = $table->getFieldName($last);
+ $cache[$key]['fieldName'] = $fieldName;
+ if ($table->isIdentifier($fieldName)) {
+ $cache[$key]['isIdentifier'] = true;
+ } else {
+ $cache[$key]['isIdentifier'] = false;
+ }
+ $type = $table->getTypeOfColumn($last);
+ if ($type == 'integer' || $type == 'string') {
+ $cache[$key]['isSimpleType'] = true;
+ } else {
+ $cache[$key]['type'] = $type;
+ $cache[$key]['isSimpleType'] = false;
+ }
+ }
+
+ $map = $this->_queryComponents[$cache[$key]['dqlAlias']];
+ $table = $map['table'];
+ $dqlAlias = $cache[$key]['dqlAlias'];
+ $fieldName = $cache[$key]['fieldName'];
+ if (isset($this->_queryComponents[$dqlAlias]['agg'][$fieldName])) {
+ $fieldName = $this->_queryComponents[$dqlAlias]['agg'][$fieldName];
+ }
+
+ if ($cache[$key]['isIdentifier']) {
+ $id[$dqlAlias] .= '|' . $value;
+ }
+
+ if ($cache[$key]['isSimpleType']) {
+ $rowData[$dqlAlias][$fieldName] = $value;
+ } else {
+ $rowData[$dqlAlias][$fieldName] = $table->prepareValue(
+ $fieldName, $value, $cache[$key]['type']);
+ }
+
+ if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
+ $nonemptyComponents[$dqlAlias] = true;
+ }
+ }
+
+ return $rowData;
+ }
+
+ protected function _getCustomIndexField($alias)
+ {
+ return isset($this->_queryComponents[$alias]['map']) ? $this->_queryComponents[$alias]['map'] : null;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Hydrator_Abstract extends IPF_ORM_Locator_Injectable
+{
+ protected $_queryComponents = array();
+
+ protected $_hydrationMode = IPF_ORM::HYDRATE_RECORD;
+
+ public function __construct() {}
+
+ public function setHydrationMode($hydrationMode)
+ {
+ $this->_hydrationMode = $hydrationMode;
+ }
+
+ public function getHydrationMode()
+ {
+ return $this->_hydrationMode;
+ }
+
+ public function setQueryComponents(array $queryComponents)
+ {
+ $this->_queryComponents = $queryComponents;
+ }
+
+ public function getQueryComponents()
+ {
+ return $this->_queryComponents;
+ }
+
+ abstract public function hydrateResultSet($stmt, $tableAliases);
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Hydrator_ArrayDriver
+{
+ public function getElementCollection($component)
+ {
+ return array();
+ }
+ public function getElement(array $data, $component)
+ {
+ return $data;
+ }
+
+ public function registerCollection($coll)
+ {
+ }
+
+ public function initRelated(array &$data, $name)
+ {
+ if ( ! isset($data[$name])) {
+ $data[$name] = array();
+ }
+ return true;
+ }
+
+ public function getNullPointer()
+ {
+ return null;
+ }
+
+ public function getLastKey(&$data)
+ {
+ end($data);
+ return key($data);
+ }
+
+ public function flush()
+ {
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Hydrator_RecordDriver extends IPF_ORM_Locator_Injectable
+{
+ protected $_collections = array();
+ protected $_tables = array();
+ private $_initializedRelations = array();
+
+ public function getElementCollection($component)
+ {
+ $coll = new IPF_ORM_Collection($component);
+ $this->_collections[] = $coll;
+
+ return $coll;
+ }
+
+ public function getLastKey($coll)
+ {
+ $coll->end();
+
+ return $coll->key();
+ }
+
+ public function initRelated(IPF_ORM_Record $record, $name)
+ {
+ if ( ! isset($this->_initializedRelations[$record->getOid()][$name])) {
+ $relation = $record->getTable()->getRelation($name);
+ $coll = new IPF_ORM_Collection($relation->getTable()->getComponentName());
+ $coll->setReference($record, $relation);
+ $record[$name] = $coll;
+ $this->_initializedRelations[$record->getOid()][$name] = true;
+ }
+ return true;
+ }
+
+ public function registerCollection(IPF_ORM_Collection $coll)
+ {
+ $this->_collections[] = $coll;
+ }
+
+ public function getNullPointer()
+ {
+ return self::$_null;
+ }
+
+ public function getElement(array $data, $component)
+ {
+ $component = $this->_getClassNameToReturn($data, $component);
+ if ( ! isset($this->_tables[$component])) {
+ $this->_tables[$component] = IPF_ORM::getTable($component);
+ $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false);
+ }
+
+ $this->_tables[$component]->setData($data);
+ $record = $this->_tables[$component]->getRecord();
+
+ return $record;
+ }
+
+ public function flush()
+ {
+ // take snapshots from all initialized collections
+ foreach ($this->_collections as $key => $coll) {
+ $coll->takeSnapshot();
+ }
+ foreach ($this->_tables as $table) {
+ $table->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, true);
+ }
+ $this->_initializedRelations = null;
+ $this->_collections = null;
+ $this->_tables = null;
+ }
+
+ protected function _getClassnameToReturn(array &$data, $component)
+ {
+ if ( ! isset($this->_tables[$component])) {
+ $this->_tables[$component] = IPF_ORM::getTable($component);
+ $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false);
+ }
+
+ if ( ! ($subclasses = $this->_tables[$component]->getOption('subclasses'))) {
+ return $component;
+ }
+
+ foreach ($subclasses as $subclass) {
+ $table = IPF_ORM::getTable($subclass);
+ $inheritanceMap = $table->getOption('inheritanceMap');
+ list($key, $value) = each($inheritanceMap);
+ if ( ! isset($data[$key]) || $data[$key] != $value) {
+ continue;
+ } else {
+ return $table->getComponentName();
+ }
+ }
+ return $component;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Import_Builder extends IPF_ORM_Builder
+{
+ protected $_path = '';
+ protected $_packagesPrefix = 'Package';
+ protected $_packagesPath = '';
+ protected $_packagesFolderName = 'packages';
+ protected $_suffix = '.php';
+ protected $_generateBaseClasses = true;
+ protected $_generateTableClasses = false;
+ protected $_baseClassPrefix = 'Base';
+ protected $_baseClassesDirectory = '_generated';
+ protected $_baseClassName = 'IPF_ORM_Record';
+ protected $_generateAccessors = false;
+ protected static $_tpl;
+
+ public function __construct()
+ {
+ $this->loadTemplate();
+ }
+
+ public function setTargetPath($path)
+ {
+ if ($path) {
+ if ( ! $this->_packagesPath) {
+ $this->setPackagesPath($path . DIRECTORY_SEPARATOR . $this->_packagesFolderName);
+ }
+
+ $this->_path = $path;
+ }
+ }
+
+ public function setPackagesPrefix($packagesPrefix)
+ {
+ $this->_packagesPrefix = $packagesPrefix;
+ }
+
+ public function setPackagesPath($packagesPath)
+ {
+ if ($packagesPath) {
+ $this->_packagesPath = $packagesPath;
+ }
+ }
+
+ public function generateBaseClasses($bool = null)
+ {
+ if ($bool !== null) {
+ $this->_generateBaseClasses = $bool;
+ }
+
+ return $this->_generateBaseClasses;
+ }
+
+ public function generateTableClasses($bool = null)
+ {
+ if ($bool !== null) {
+ $this->_generateTableClasses = $bool;
+ }
+
+ return $this->_generateTableClasses;
+ }
+
+ public function generateAccessors($bool = null)
+ {
+ if ($bool !== null) {
+ $this->_generateAccessors = $bool;
+ }
+ return $this->_generateAccessors;
+ }
+
+ public function setBaseClassPrefix($prefix)
+ {
+ $this->_baseClassPrefix = $prefix;
+ }
+
+ public function getBaseClassPrefix()
+ {
+ return $this->_baseClassPrefix;
+ }
+
+ public function setBaseClassesDirectory($baseClassesDirectory)
+ {
+ $this->_baseClassesDirectory = $baseClassesDirectory;
+ }
+
+ public function setBaseClassName($className)
+ {
+ $this->_baseClassName = $className;
+ }
+
+ public function setSuffix($suffix)
+ {
+ $this->_suffix = $suffix;
+ }
+
+ public function getTargetPath()
+ {
+ return $this->_path;
+ }
+
+ public function setOptions($options)
+ {
+ if ( ! empty($options)) {
+ foreach ($options as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+ }
+
+ public function setOption($key, $value)
+ {
+ $name = 'set' . IPF_ORM_Inflector::classify($key);
+
+ if (method_exists($this, $name)) {
+ $this->$name($value);
+ } else {
+ $key = '_' . $key;
+ $this->$key = $value;
+ }
+ }
+
+ public function loadTemplate()
+ {
+ if (isset(self::$_tpl)) {
+ return;
+ }
+
+ self::$_tpl = '/**' . PHP_EOL
+ . ' * This class has been auto-generated by the IPF ORM Framework' . PHP_EOL
+ . ' */' . PHP_EOL
+ . '%sclass %s extends %s' . PHP_EOL
+ . '{'
+ . '%s' . PHP_EOL
+ . '%s' . PHP_EOL
+ . '%s'
+ . '}';
+ }
+
+ public function buildTableDefinition(array $definition)
+ {
+ if (isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
+ return;
+ }
+
+ $ret = array();
+
+ $i = 0;
+
+ if (isset($definition['inheritance']['type']) && $definition['inheritance']['type'] == 'concrete') {
+ $ret[$i] = " parent::setTableDefinition();";
+ $i++;
+ }
+
+ if (isset($definition['tableName']) && !empty($definition['tableName'])) {
+ $ret[$i] = " ".'$this->setTableName(\''. $definition['tableName'].'\');';
+ $i++;
+ }
+
+ if (isset($definition['columns']) && is_array($definition['columns']) && !empty($definition['columns'])) {
+ $ret[$i] = $this->buildColumns($definition['columns']);
+ $i++;
+ }
+
+ if (isset($definition['indexes']) && is_array($definition['indexes']) && !empty($definition['indexes'])) {
+ $ret[$i] = $this->buildIndexes($definition['indexes']);
+ $i++;
+ }
+
+ if (isset($definition['attributes']) && is_array($definition['attributes']) && !empty($definition['attributes'])) {
+ $ret[$i] = $this->buildAttributes($definition['attributes']);
+ $i++;
+ }
+
+ if (isset($definition['options']) && is_array($definition['options']) && !empty($definition['options'])) {
+ $ret[$i] = $this->buildOptions($definition['options']);
+ $i++;
+ }
+
+ if (isset($definition['inheritance']['subclasses']) && ! empty($definition['inheritance']['subclasses'])) {
+ $ret[$i] = " ".'$this->setSubClasses('. $this->varExport($definition['inheritance']['subclasses']).');';
+ $i++;
+ }
+
+ $code = implode(PHP_EOL, $ret);
+ $code = trim($code);
+
+ return PHP_EOL . " public function setTableDefinition()" . PHP_EOL . ' {' . PHP_EOL . ' ' . $code . PHP_EOL . ' }';
+ }
+
+ public function buildSetUp(array $definition)
+ {
+ $ret = array();
+ $i = 0;
+
+ if (isset($definition['relations']) && is_array($definition['relations']) && ! empty($definition['relations'])) {
+ foreach ($definition['relations'] as $name => $relation) {
+ $class = isset($relation['class']) ? $relation['class']:$name;
+ $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? ' as ' . $relation['alias'] : '';
+
+ if ( ! isset($relation['type'])) {
+ $relation['type'] = IPF_ORM_Relation::ONE;
+ }
+
+ if ($relation['type'] === IPF_ORM_Relation::ONE ||
+ $relation['type'] === IPF_ORM_Relation::ONE_COMPOSITE) {
+ $ret[$i] = " ".'$this->hasOne(\'' . $class . $alias . '\'';
+ } else {
+ $ret[$i] = " ".'$this->hasMany(\'' . $class . $alias . '\'';
+ }
+
+ $a = array();
+
+ if (isset($relation['refClass'])) {
+ $a[] = '\'refClass\' => ' . $this->varExport($relation['refClass']);
+ }
+
+ if (isset($relation['deferred']) && $relation['deferred']) {
+ $a[] = '\'default\' => ' . $this->varExport($relation['deferred']);
+ }
+
+ if (isset($relation['local']) && $relation['local']) {
+ $a[] = '\'local\' => ' . $this->varExport($relation['local']);
+ }
+
+ if (isset($relation['foreign']) && $relation['foreign']) {
+ $a[] = '\'foreign\' => ' . $this->varExport($relation['foreign']);
+ }
+
+ if (isset($relation['onDelete']) && $relation['onDelete']) {
+ $a[] = '\'onDelete\' => ' . $this->varExport($relation['onDelete']);
+ }
+
+ if (isset($relation['onUpdate']) && $relation['onUpdate']) {
+ $a[] = '\'onUpdate\' => ' . $this->varExport($relation['onUpdate']);
+ }
+
+ if (isset($relation['equal']) && $relation['equal']) {
+ $a[] = '\'equal\' => ' . $this->varExport($relation['equal']);
+ }
+
+ if (isset($relation['owningSide']) && $relation['owningSide']) {
+ $a[] = '\'owningSide\' => ' . $this->varExport($relation['owningSide']);
+ }
+
+ if ( ! empty($a)) {
+ $ret[$i] .= ', ' . 'array(';
+ $length = strlen($ret[$i]);
+ $ret[$i] .= implode(',' . PHP_EOL . str_repeat(' ', $length), $a) . ')';
+ }
+
+ $ret[$i] .= ');'.PHP_EOL;
+ $i++;
+ }
+ }
+
+ if (isset($definition['templates']) && is_array($definition['templates']) && !empty($definition['templates'])) {
+ $ret[$i] = $this->buildTemplates($definition['templates']);
+ $i++;
+ }
+
+ if (isset($definition['actAs']) && is_array($definition['actAs']) && !empty($definition['actAs'])) {
+ $ret[$i] = $this->buildActAs($definition['actAs']);
+ $i++;
+ }
+
+ if (isset($definition['listeners']) && is_array($definition['listeners']) && !empty($definition['listeners'])) {
+ foreach($definition['listeners'] as $listener)
+ {
+ $ret[$i] = $this->buildListener($listener);
+ $i++;
+ }
+ }
+
+ $code = implode(PHP_EOL, $ret);
+ $code = trim($code);
+
+ // If the body of the function has contents then we need to
+ if ($code) {
+ // If the body of the function has contents and we are using inheritance
+ // then we need call the parent::setUp() before the body of the function
+ // Class table inheritance is the only one we shouldn't call parent::setUp() for
+ if ($code && isset($definition['inheritance']['type']) && $definition['inheritance']['type'] != 'class_table') {
+ $code = "parent::setUp();" . PHP_EOL . ' ' . $code;
+ }
+ }
+
+ // If we have some code for the function then lets define it and return it
+ if ($code) {
+ return ' public function setUp()' . PHP_EOL . ' {' . PHP_EOL . ' ' . $code . PHP_EOL . ' }';
+ }
+ }
+
+ public function buildColumns(array $columns)
+ {
+ $build = null;
+ foreach ($columns as $name => $column) {
+ $columnName = isset($column['name']) ? $column['name']:$name;
+ $build .= " ".'$this->hasColumn(\'' . $columnName . '\', \'' . $column['type'] . '\'';
+
+ if ($column['length']) {
+ $build .= ', ' . $column['length'];
+ } else {
+ $build .= ', null';
+ }
+
+ $options = $column;
+
+ // Remove name, alltypes, ntype. They are not needed in options array
+ unset($options['name']);
+ unset($options['alltypes']);
+ unset($options['ntype']);
+
+ // Remove notnull => true if the column is primary
+ // Primary columns are implied to be notnull in IPF
+ if (isset($options['primary']) && $options['primary'] == true && (isset($options['notnull']) && $options['notnull'] == true)) {
+ unset($options['notnull']);
+ }
+
+ // Remove default if the value is 0 and the column is a primary key
+ // IPF defaults to 0 if it is a primary key
+ if (isset($options['primary']) && $options['primary'] == true && (isset($options['default']) && $options['default'] == 0)) {
+ unset($options['default']);
+ }
+
+ // These can be removed if they are empty. They all default to a false/0/null value anyways
+ $remove = array('fixed', 'primary', 'notnull', 'autoincrement', 'unsigned');
+ foreach ($remove as $key) {
+ if (isset($options[$key]) && empty($options[$key])) {
+ unset($options[$key]);
+ }
+ }
+
+ // Remove null and empty array values
+ foreach ($options as $key => $value) {
+ if (is_null($value) || (is_array($value) && empty($value))) {
+ unset($options[$key]);
+ }
+ }
+
+ if (is_array($options) && !empty($options)) {
+ $build .= ', ' . $this->varExport($options);
+ }
+
+ $build .= ');' . PHP_EOL;
+ }
+
+ return $build;
+ }
+
+ public function buildAccessors(array $definition)
+ {
+ $accessors = array();
+ foreach (array_keys($definition['columns']) as $name) {
+ $accessors[] = $name;
+ }
+
+ foreach ($definition['relations'] as $relation) {
+ $accessors[] = $relation['alias'];
+ }
+
+ $ret = '';
+ foreach ($accessors as $name) {
+ // getters
+ $ret .= PHP_EOL . ' public function get' . IPF_ORM_Inflector::classify(IPF_ORM_Inflector::tableize($name)) . "(\$load = true)" . PHP_EOL;
+ $ret .= " {" . PHP_EOL;
+ $ret .= " return \$this->get('{$name}', \$load);" . PHP_EOL;
+ $ret .= " }" . PHP_EOL;
+
+ // setters
+ $ret .= PHP_EOL . ' public function set' . IPF_ORM_Inflector::classify(IPF_ORM_Inflector::tableize($name)) . "(\${$name}, \$load = true)" . PHP_EOL;
+ $ret .= " {" . PHP_EOL;
+ $ret .= " return \$this->set('{$name}', \${$name}, \$load);" . PHP_EOL;
+ $ret .= " }" . PHP_EOL;
+ }
+
+ return $ret;
+ }
+
+ public function buildTemplates(array $templates)
+ {
+ $build = '';
+ foreach ($templates as $name => $options) {
+
+ if (is_array($options) && !empty($options)) {
+ $optionsPhp = $this->varExport($options);
+
+ $build .= " \$this->loadTemplate('" . $name . "', " . $optionsPhp . ");" . PHP_EOL;
+ } else {
+ if (isset($templates[0])) {
+ $build .= " \$this->loadTemplate('" . $options . "');" . PHP_EOL;
+ } else {
+ $build .= " \$this->loadTemplate('" . $name . "');" . PHP_EOL;
+ }
+ }
+ }
+
+ return $build;
+ }
+
+ private function emitAssign($level, $name, $option)
+ {
+ // find class matching $name
+ $classname = $name;
+ if (class_exists("IPF_ORM_Template_$name", true)) {
+ $classname = "IPF_ORM_Template_$name";
+ }
+ return " \$" . strtolower($name) . "$level = new $classname($option);". PHP_EOL;
+ }
+
+ private function emitAddChild($level, $parent, $name)
+ {
+ return " \$" . strtolower($parent) . ($level - 1) . "->addChild(\$" . strtolower($name) . "$level);" . PHP_EOL;
+ }
+
+ private function emitActAs($level, $name)
+ {
+ return " \$this->actAs(\$" . strtolower($name) . "$level);" . PHP_EOL;
+ }
+
+ public function buildActAs($actAs)
+ {
+ $emittedActAs = array();
+ $build = $this->innerBuildActAs($actAs, 0, null, $emittedActAs);
+ foreach($emittedActAs as $str) {
+ $build .= $str;
+ }
+ return $build;
+ }
+
+ private function innerBuildActAs($actAs, $level = 0, $parent = null, array &$emittedActAs)
+ {
+ // rewrite special case of actAs: [Behavior] which gave [0] => Behavior
+ if(is_array($actAs) && isset($actAs[0]) && !is_array($actAs[0])) {
+ $actAs = array_flip($actAs);
+ }
+
+ $build = '';
+ $currentParent = $parent;
+ if(is_array($actAs)) {
+ foreach($actAs as $template => $options) {
+ if ($template == 'actAs') {
+ // found another actAs
+ $build .= $this->innerBuildActAs($options, $level + 1, $parent, $emittedActAs);
+ } else if (is_array($options)) {
+ // remove actAs from options
+ $realOptions = array();
+ $leftActAs = array();
+ foreach($options as $name => $value) {
+ if ($name != 'actAs') {
+ $realOptions[$name] = $options[$name];
+ } else {
+ $leftActAs[$name] = $options[$name];
+ }
+ }
+
+ $optionPHP = $this->varExport($realOptions);
+ $build .= $this->emitAssign($level, $template, $optionPHP);
+ if ($level == 0) {
+ $emittedActAs[] = $this->emitActAs($level, $template);
+ } else {
+ $build .= $this->emitAddChild($level, $currentParent, $template);
+ }
+ // descend for the remainings actAs
+ $parent = $template;
+ $build .= $this->innerBuildActAs($leftActAs, $level, $template, $emittedActAs);
+ } else {
+ $build .= $this->emitAssign($level, $template, null);
+ if ($level == 0) {
+ $emittedActAs[] = $this->emitActAs($level, $template);
+ } else {
+ $build .= $this->emitAddChild($level, $currentParent, $template);
+ }
+ $parent = $template;
+ }
+ }
+ } else {
+ $build .= $this->emitAssign($level, $actAs, null);
+ if ($level == 0) {
+ $emittedActAs[] = $this->emitActAs($level, $actAs);
+ } else {
+ $build .= $this->emitAddChild($level, $currentParent, $actAs);
+ }
+ }
+
+ return $build;
+ }
+
+ public function buildListener($listener)
+ {
+ return PHP_EOL." ".'$this->addListener(new '.$listener.'());';
+ }
+
+ public function buildAttributes(array $attributes)
+ {
+ $build = PHP_EOL;
+ foreach ($attributes as $key => $value) {
+
+ if (is_bool($value))
+ {
+ $values = $value ? 'true':'false';
+ } else {
+ if ( ! is_array($value)) {
+ $value = array($value);
+ }
+
+ $values = '';
+ foreach ($value as $attr) {
+ $values .= "IPF_ORM::" . strtoupper($key) . "_" . strtoupper($attr) . ' ^ ';
+ }
+
+ // Trim last ^
+ $values = substr($values, 0, strlen($values) - 3);
+ }
+
+ $build .= " \$this->setAttribute(IPF_ORM::ATTR_" . strtoupper($key) . ", " . $values . ");" . PHP_EOL;
+ }
+
+ return $build;
+ }
+
+ public function buildOptions(array $options)
+ {
+ $build = '';
+ foreach ($options as $name => $value) {
+ $build .= " \$this->option('$name', " . $this->varExport($value) . ");" . PHP_EOL;
+ }
+
+ return $build;
+ }
+
+ public function buildIndexes(array $indexes)
+ {
+ $build = '';
+
+ foreach ($indexes as $indexName => $definitions) {
+ $build .= PHP_EOL . " \$this->index('" . $indexName . "'";
+ $build .= ', ' . $this->varExport($definitions);
+ $build .= ');';
+ }
+
+ return $build;
+ }
+
+ public function buildDefinition(array $definition)
+ {
+ if ( ! isset($definition['className'])) {
+ throw new IPF_ORM_Exception('Missing class name.');
+ }
+
+ $abstract = isset($definition['abstract']) && $definition['abstract'] === true ? 'abstract ':null;
+ $className = $definition['className'];
+ $extends = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends']:$this->_baseClassName;
+
+ if ( ! (isset($definition['no_definition']) && $definition['no_definition'] === true)) {
+ $tableDefinitionCode = $this->buildTableDefinition($definition);
+ $setUpCode = $this->buildSetUp($definition);
+ } else {
+ $tableDefinitionCode = null;
+ $setUpCode = null;
+ }
+
+ if ($tableDefinitionCode && $setUpCode) {
+ $setUpCode = PHP_EOL . $setUpCode;
+ }
+
+ if ( ! isset($definition['generate_accessors']) || !$definition['generate_accessors']) {
+ $definition['generate_accessors'] = $this->generateAccessors();
+ }
+
+ $accessorsCode = (isset($definition['generate_accessors']) && $definition['generate_accessors'] === true) ? $this->buildAccessors($definition):null;
+
+ $content = sprintf(self::$_tpl, $abstract,
+ $className,
+ $extends,
+ $tableDefinitionCode,
+ $setUpCode,
+ $accessorsCode);
+
+ return $content;
+ }
+
+ public function buildRecord(array $definition)
+ {
+ if ( ! isset($definition['className'])) {
+ throw new IPF_ORM_Exception('Missing class name.');
+ }
+
+ $definition['topLevelClassName'] = $definition['className'];
+
+ if ($this->generateBaseClasses()) {
+ $definition['is_package'] = (isset($definition['package']) && $definition['package']) ? true:false;
+
+ if ($definition['is_package']) {
+ $e = explode('.', trim($definition['package']));
+ $definition['package_name'] = $e[0];
+
+ $definition['package_path'] = ! empty($e) ? implode(DIRECTORY_SEPARATOR, $e):$definition['package_name'];
+ }
+ // Top level definition that extends from all the others
+ $topLevel = $definition;
+ unset($topLevel['tableName']);
+
+ // If we have a package then we need to make this extend the package definition and not the base definition
+ // The package definition will then extends the base definition
+ $topLevel['inheritance']['extends'] = (isset($topLevel['package']) && $topLevel['package']) ? $this->_packagesPrefix . $topLevel['className']:$this->_baseClassPrefix . $topLevel['className'];
+ $topLevel['no_definition'] = true;
+ $topLevel['generate_once'] = true;
+ $topLevel['is_main_class'] = true;
+ unset($topLevel['connection']);
+
+ // Package level definition that extends from the base definition
+ if (isset($definition['package'])) {
+
+ $packageLevel = $definition;
+ $packageLevel['className'] = $topLevel['inheritance']['extends'];
+ $packageLevel['inheritance']['extends'] = $this->_baseClassPrefix . $topLevel['className'];
+ $packageLevel['no_definition'] = true;
+ $packageLevel['abstract'] = true;
+ $packageLevel['override_parent'] = true;
+ $packageLevel['generate_once'] = true;
+ $packageLevel['is_package_class'] = true;
+ unset($packageLevel['connection']);
+
+ $packageLevel['tableClassName'] = $packageLevel['className'] . 'Table';
+ $packageLevel['inheritance']['tableExtends'] = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends'] . 'Table':'IPF_ORM_Table';
+
+ $topLevel['tableClassName'] = $topLevel['topLevelClassName'] . 'Table';
+ $topLevel['inheritance']['tableExtends'] = $packageLevel['className'] . 'Table';
+ } else {
+ $topLevel['tableClassName'] = $topLevel['className'] . 'Table';
+ $topLevel['inheritance']['tableExtends'] = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends'] . 'Table':'IPF_ORM_Table';
+ }
+
+ $baseClass = $definition;
+ $baseClass['className'] = $this->_baseClassPrefix . $baseClass['className'];
+ $baseClass['abstract'] = true;
+ $baseClass['override_parent'] = false;
+ $baseClass['is_base_class'] = true;
+
+ $this->writeDefinition($baseClass);
+
+ if ( ! empty($packageLevel)) {
+ $this->writeDefinition($packageLevel);
+ }
+
+ $this->writeDefinition($topLevel);
+ } else {
+ $this->writeDefinition($definition);
+ }
+ }
+
+ public function writeTableDefinition($className, $path, $options = array())
+ {
+ $content = '<?php' . PHP_EOL;
+ $content .= sprintf(self::$_tpl, false,
+ $className,
+ isset($options['extends']) ? $options['extends']:'IPF_ORM_Table',
+ null,
+ null,
+ null
+ );
+
+ IPF_ORM_Utils::makeDirectories($path);
+
+ $writePath = $path . DIRECTORY_SEPARATOR . $className . $this->_suffix;
+
+ IPF_ORM::loadModel($className, $writePath);
+
+ if ( ! file_exists($writePath)) {
+ file_put_contents($writePath, $content);
+ }
+ }
+
+ public function writeDefinition(array $definition)
+ {
+ $definitionCode = $this->buildDefinition($definition);
+
+ $fileName = $definition['className'] . $this->_suffix;
+
+ $packagesPath = $this->_packagesPath ? $this->_packagesPath:$this->_path;
+
+ // If this is a main class that either extends from Base or Package class
+ if (isset($definition['is_main_class']) && $definition['is_main_class']) {
+ // If is package then we need to put it in a package subfolder
+ if (isset($definition['is_package']) && $definition['is_package']) {
+ $writePath = $this->_path . DIRECTORY_SEPARATOR . $definition['package_name'];
+ // Otherwise lets just put it in the root of the path
+ } else {
+ $writePath = $this->_path;
+ }
+
+ if ($this->generateTableClasses()) {
+ $this->writeTableDefinition($definition['tableClassName'], $writePath, array('extends' => $definition['inheritance']['tableExtends']));
+ }
+ }
+ // If is the package class then we need to make the path to the complete package
+ else if (isset($definition['is_package_class']) && $definition['is_package_class']) {
+ $writePath = $packagesPath . DIRECTORY_SEPARATOR . $definition['package_path'];
+
+ if ($this->generateTableClasses()) {
+ $this->writeTableDefinition($definition['tableClassName'], $writePath, array('extends' => $definition['inheritance']['tableExtends']));
+ }
+ }
+ // If it is the base class of the ipf record definition
+ else if (isset($definition['is_base_class']) && $definition['is_base_class']) {
+ // If it is a part of a package then we need to put it in a package subfolder
+ if (isset($definition['is_package']) && $definition['is_package']) {
+ $basePath = $this->_path . DIRECTORY_SEPARATOR . $definition['package_name'];
+ $writePath = $basePath . DIRECTORY_SEPARATOR . $this->_baseClassesDirectory;
+ // Otherwise lets just put it in the root generated folder
+ } else {
+ $writePath = $this->_path . DIRECTORY_SEPARATOR . $this->_baseClassesDirectory;
+ }
+ }
+
+ // If we have a writePath from the if else conditionals above then use it
+ if (isset($writePath)) {
+ IPF_ORM_Utils::makeDirectories($writePath);
+
+ $writePath .= DIRECTORY_SEPARATOR . $fileName;
+ // Otherwise none of the conditions were met and we aren't generating base classes
+ } else {
+ IPF_ORM_Utils::makeDirectories($this->_path);
+
+ $writePath = $this->_path . DIRECTORY_SEPARATOR . $fileName;
+ }
+
+ $code = "<?php" . PHP_EOL;
+
+ if (isset($definition['connection']) && $definition['connection']) {
+ $code .= "// Connection Component Binding" . PHP_EOL;
+ $code .= "IPF_ORM_Manager::getInstance()->bindComponent('" . $definition['connectionClassName'] . "', '" . $definition['connection'] . "');" . PHP_EOL;
+ }
+
+ $code .= PHP_EOL . $definitionCode;
+
+ if (isset($definition['generate_once']) && $definition['generate_once'] === true) {
+ if ( ! file_exists($writePath)) {
+ $bytes = file_put_contents($writePath, $code);
+ }
+ } else {
+ $bytes = file_put_contents($writePath, $code);
+ }
+
+ if (isset($bytes) && $bytes === false) {
+ throw new IPF_ORM_Exception("Couldn't write file " . $writePath);
+ }
+
+ IPF_ORM::loadModel($definition['className'], $writePath);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Import_Schema
+{
+ protected $_relations = array();
+
+ protected $_options = array('packagesPrefix' => 'Package',
+ 'packagesPath' => '',
+ 'packagesFolderName' => 'packages',
+ 'suffix' => '.php',
+ 'generateBaseClasses' => true,
+ 'generateTableClasses' => false,
+ 'generateAccessors' => false,
+ 'baseClassesPrefix' => 'Base',
+ 'baseClassesDirectory' => '_generated',
+ 'baseClassName' => 'IPF_ORM_Record');
+
+ protected $_validation = array('root' => array('abstract',
+ 'connection',
+ 'className',
+ 'tableName',
+ 'connection',
+ 'relations',
+ 'columns',
+ 'indexes',
+ 'attributes',
+ 'templates',
+ 'actAs',
+ 'options',
+ 'package',
+ 'inheritance',
+ 'detect_relations',
+ 'generate_accessors',
+ 'listeners'),
+
+ 'column' => array('name',
+ 'format',
+ 'fixed',
+ 'primary',
+ 'autoincrement',
+ 'type',
+ 'length',
+ 'size',
+ 'default',
+ 'scale',
+ 'values',
+ 'comment',
+ 'sequence',
+ 'protected',
+ 'zerofill',
+ 'owner'),
+
+ 'relation' => array('key',
+ 'class',
+ 'alias',
+ 'type',
+ 'refClass',
+ 'local',
+ 'foreign',
+ 'foreignClass',
+ 'foreignAlias',
+ 'foreignType',
+ 'autoComplete',
+ 'onDelete',
+ 'onUpdate',
+ 'equal',
+ 'owningSide'),
+
+ 'inheritance'=> array('type',
+ 'extends',
+ 'keyField',
+ 'keyValue'));
+
+ protected $_validators = array();
+ public function getValidators()
+ {
+ if (empty($this->_validators)) {
+ $this->_validators = IPF_ORM_Utils::getValidators();
+ }
+
+ return $this->_validators;
+ }
+
+ public function getOption($name)
+ {
+ if (isset($this->_options[$name])) {
+ return $this->_options[$name];
+ }
+ }
+
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ public function setOption($name, $value)
+ {
+ if (isset($this->_options[$name])) {
+ $this->_options[$name] = $value;
+ }
+ }
+
+ public function setOptions($options)
+ {
+ if ( ! empty($options)) {
+ $this->_options = $options;
+ }
+ }
+
+ public function buildSchema($schema, $format)
+ {
+ $array = array();
+
+ foreach ((array) $schema AS $s) {
+ if (is_file($s)) {
+ $array = array_merge($array, $this->parseSchema($s, $format));
+ } else if (is_dir($s)) {
+ $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($s),
+ RecursiveIteratorIterator::LEAVES_ONLY);
+
+ foreach ($it as $file) {
+ $e = explode('.', $file->getFileName());
+ if (end($e) === $format) {
+ $array = array_merge($array, $this->parseSchema($file->getPathName(), $format));
+ }
+ }
+ } else {
+ $array = array_merge($array, $this->parseSchema($s, $format));
+ }
+ }
+
+ $array = $this->_buildRelationships($array);
+ $array = $this->_processInheritance($array);
+
+ return $array;
+ }
+
+ public function importSchema($schema, $format = 'yml', $directory = null, $models = array())
+ {
+ $builder = new IPF_ORM_Import_Builder();
+ $builder->setTargetPath($directory);
+ $builder->setOptions($this->getOptions());
+
+ $array = $this->buildSchema($schema, $format);
+
+ foreach ($array as $name => $definition) {
+ if ( ! empty($models) && !in_array($definition['className'], $models)) {
+ continue;
+ }
+ print " $name\n";
+ $builder->buildRecord($definition);
+ }
+ }
+
+ public function parseSchema($schema, $type)
+ {
+ $defaults = array('abstract' => false,
+ 'className' => null,
+ 'tableName' => null,
+ 'connection' => null,
+ 'relations' => array(),
+ 'indexes' => array(),
+ 'attributes' => array(),
+ 'templates' => array(),
+ 'actAs' => array(),
+ 'options' => array(),
+ 'package' => null,
+ 'inheritance' => array(),
+ 'detect_relations' => false,
+ 'generate_accessors' => false);
+
+ $array = IPF_ORM_Parser::load($schema, $type);
+
+ // Go through the schema and look for global values so we can assign them to each table/class
+ $globals = array();
+ $globalKeys = array('connection',
+ 'attributes',
+ 'templates',
+ 'actAs',
+ 'options',
+ 'package',
+ 'inheritance',
+ 'detect_relations',
+ 'generate_accessors');
+
+ // Loop over and build up all the global values and remove them from the array
+ foreach ($array as $key => $value) {
+ if (in_array($key, $globalKeys)) {
+ unset($array[$key]);
+ $globals[$key] = $value;
+ }
+ }
+
+ // Apply the globals to each table if it does not have a custom value set already
+ foreach ($array as $className => $table) {
+ foreach ($globals as $key => $value) {
+ if (!isset($array[$className][$key])) {
+ $array[$className][$key] = $value;
+ }
+ }
+ }
+
+ $build = array();
+
+ foreach ($array as $className => $table) {
+ $this->_validateSchemaElement('root', array_keys($table), $className);
+
+ $columns = array();
+
+ $className = isset($table['className']) ? (string) $table['className']:(string) $className;
+
+ if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) {
+ $table['inheritance']['type'] = 'column_aggregation';
+ }
+
+ if (isset($table['tableName']) && $table['tableName']) {
+ $tableName = $table['tableName'];
+ } else {
+ if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) {
+ $tableName = null;
+ } else {
+ $tableName = IPF_ORM_Inflector::tableize($className);
+ }
+ }
+
+ $connection = isset($table['connection']) ? $table['connection']:'current';
+
+ $columns = isset($table['columns']) ? $table['columns']:array();
+
+ if ( ! empty($columns)) {
+ foreach ($columns as $columnName => $field) {
+
+ // Support short syntax: my_column: integer(4)
+ if ( ! is_array($field)) {
+ $original = $field;
+ $field = array();
+ $field['type'] = $original;
+ }
+
+ $colDesc = array();
+ if (isset($field['name'])) {
+ $colDesc['name'] = $field['name'];
+ } else {
+ $colDesc['name'] = $columnName;
+ }
+
+ $this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']);
+
+ // Support short type(length) syntax: my_column: { type: integer(4) }
+ $e = explode('(', $field['type']);
+ if (isset($e[0]) && isset($e[1])) {
+ $colDesc['type'] = $e[0];
+ $colDesc['length'] = substr($e[1], 0, strlen($e[1]) - 1);
+ } else {
+ $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
+ $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
+ $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
+ }
+
+ $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
+ $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
+ $colDesc['default'] = isset($field['default']) ? $field['default']:null;
+ $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
+ $colDesc['sequence'] = isset($field['sequence']) ? (string) $field['sequence']:null;
+ $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null;
+
+ // Include all the specified and valid validators in the colDesc
+ $validators = $this->getValidators();
+
+ foreach ($validators as $validator) {
+ if (isset($field[$validator])) {
+ $colDesc[$validator] = $field[$validator];
+ }
+ }
+
+ $columns[(string) $columnName] = $colDesc;
+ }
+ }
+
+ // Apply the default values
+ foreach ($defaults as $key => $defaultValue) {
+ if (isset($table[$key]) && ! isset($build[$className][$key])) {
+ $build[$className][$key] = $table[$key];
+ } else {
+ $build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue;
+ }
+ }
+
+ $build[$className]['className'] = $className;
+ $build[$className]['tableName'] = $tableName;
+ $build[$className]['columns'] = $columns;
+
+ // Make sure that anything else that is specified in the schema makes it to the final array
+ $build[$className] = IPF_ORM_Utils::arrayDeepMerge($table, $build[$className]);
+
+ // We need to keep track of the className for the connection
+ $build[$className]['connectionClassName'] = $build[$className]['className'];
+ }
+
+ return $build;
+ }
+
+ protected function _processInheritance($array)
+ {
+ // Apply default inheritance configuration
+ foreach ($array as $className => $definition) {
+ if ( ! empty($array[$className]['inheritance'])) {
+ $this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance');
+
+ // Default inheritance to concrete inheritance
+ if ( ! isset($array[$className]['inheritance']['type'])) {
+ $array[$className]['inheritance']['type'] = 'concrete';
+ }
+
+ // Some magic for setting up the keyField and keyValue column aggregation options
+ // Adds keyField to the parent class automatically
+ if ($array[$className]['inheritance']['type'] == 'column_aggregation') {
+ // Set the keyField to 'type' by default
+ if ( ! isset($array[$className]['inheritance']['keyField'])) {
+ $array[$className]['inheritance']['keyField'] = 'type';
+ }
+
+ // Set the keyValue to the name of the child class if it does not exist
+ if ( ! isset($array[$className]['inheritance']['keyValue'])) {
+ $array[$className]['inheritance']['keyValue'] = $className;
+ }
+
+ // Add the keyType column to the parent if a definition does not already exist
+ if ( ! isset($array[$array[$className]['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']])) {
+ $array[$definition['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255);
+ }
+ }
+ }
+ }
+
+ // Array of the array keys to move to the parent, and the value to default the child definition to
+ // after moving it. Will also populate the subclasses array for the inheritance parent
+ $moves = array('columns' => array());
+
+ foreach ($array as $className => $definition) {
+ $parent = $this->_findBaseSuperClass($array, $definition['className']);
+ // Move any definitions on the schema to the parent
+ if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
+ foreach ($moves as $move => $resetValue) {
+ $array[$parent][$move] = IPF_ORM_Utils::arrayDeepMerge($array[$parent][$move], $definition[$move]);
+ $array[$definition['className']][$move] = $resetValue;
+ }
+
+ // Populate the parents subclasses
+ if ($definition['inheritance']['type'] == 'column_aggregation') {
+ $array[$parent]['inheritance']['subclasses'][$definition['className']] = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']);
+ }
+ }
+ }
+
+ return $array;
+ }
+
+ protected function _findBaseSuperClass($array, $class)
+ {
+ if (isset($array[$class]['inheritance']['extends'])) {
+ return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']);
+ } else {
+ return $class;
+ }
+ }
+
+ protected function _buildRelationships($array)
+ {
+ // Handle auto detecting relations by the names of columns
+ // User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id
+ foreach ($array as $className => $properties) {
+ if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) {
+ foreach ($properties['columns'] as $column) {
+ // Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find
+ // the class name for the column
+ if (strpos($column['name'], '_id')) {
+ $columnClassName = IPF_ORM_Inflector::classify(str_replace('_id', '', $column['name']));
+ if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) {
+ $array[$className]['relations'][$columnClassName] = array();
+
+ // Set the detected foreign key type and length to the same as the primary key
+ // of the related table
+ $type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer';
+ $length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8;
+ $array[$className]['columns'][$column['name']]['type'] = $type;
+ $array[$className]['columns'][$column['name']]['length'] = $length;
+ }
+ }
+ }
+ }
+ }
+
+ foreach ($array as $name => $properties) {
+ if ( ! isset($properties['relations'])) {
+ continue;
+ }
+
+ $className = $properties['className'];
+ $relations = $properties['relations'];
+
+ foreach ($relations as $alias => $relation) {
+ $class = isset($relation['class']) ? $relation['class']:$alias;
+ if ( ! isset($array[$class])) {
+ continue;
+ }
+ $relation['class'] = $class;
+ $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
+
+ // Attempt to guess the local and foreign
+ if (isset($relation['refClass'])) {
+ $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($name) . '_id';
+ $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:IPF_ORM_Inflector::tableize($class) . '_id';
+ } else {
+ $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($relation['class']) . '_id';
+ $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
+ }
+
+ if (isset($relation['refClass'])) {
+ $relation['type'] = 'many';
+ }
+
+ if (isset($relation['type']) && $relation['type']) {
+ $relation['type'] = $relation['type'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY;
+ } else {
+ $relation['type'] = IPF_ORM_Relation::ONE;
+ }
+
+ if (isset($relation['foreignType']) && $relation['foreignType']) {
+ $relation['foreignType'] = $relation['foreignType'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY;
+ }
+
+ $relation['key'] = $this->_buildUniqueRelationKey($relation);
+
+ $this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']);
+
+ $this->_relations[$className][$alias] = $relation;
+ }
+ }
+
+ // Now we auto-complete opposite ends of relationships
+ $this->_autoCompleteOppositeRelations();
+
+ // Make sure we do not have any duplicate relations
+ $this->_fixDuplicateRelations();
+
+ // Set the full array of relationships for each class to the final array
+ foreach ($this->_relations as $className => $relations) {
+ $array[$className]['relations'] = $relations;
+ }
+
+ return $array;
+ }
+
+ protected function _autoCompleteOppositeRelations()
+ {
+ foreach($this->_relations as $className => $relations) {
+ foreach ($relations AS $alias => $relation) {
+ if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) {
+ continue;
+ }
+
+ $newRelation = array();
+ $newRelation['foreign'] = $relation['local'];
+ $newRelation['local'] = $relation['foreign'];
+ $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass']:$className;
+ $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias']:$className;
+
+ // this is so that we know that this relation was autogenerated and
+ // that we do not need to include it if it is explicitly declared in the schema by the users.
+ $newRelation['autogenerated'] = true;
+
+ if (isset($relation['refClass'])) {
+ $newRelation['refClass'] = $relation['refClass'];
+ $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type'];
+ } else {
+ if(isset($relation['foreignType'])) {
+ $newRelation['type'] = $relation['foreignType'];
+ } else {
+ $newRelation['type'] = $relation['type'] === IPF_ORM_Relation::ONE ? IPF_ORM_Relation::MANY:IPF_ORM_Relation::ONE;
+ }
+ }
+
+ // Make sure it doesn't already exist
+ if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) {
+ $newRelation['key'] = $this->_buildUniqueRelationKey($newRelation);
+ $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation;
+ }
+ }
+ }
+ }
+
+ protected function _fixDuplicateRelations()
+ {
+ foreach($this->_relations as $className => $relations) {
+ // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations
+ $existingRelations = array();
+ $uniqueRelations = array();
+ foreach ($relations as $relation) {
+ if ( ! in_array($relation['key'], $existingRelations)) {
+ $existingRelations[] = $relation['key'];
+ $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
+ } else {
+ // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it
+ if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) {
+ $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
+ }
+ }
+ }
+
+ $this->_relations[$className] = $uniqueRelations;
+ }
+ }
+
+ protected function _buildUniqueRelationKey($relation)
+ {
+ return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null));
+ }
+
+ protected function _validateSchemaElement($name, $element, $path)
+ {
+ $element = (array) $element;
+
+ $validation = $this->_validation[$name];
+
+ // Validators are a part of the column validation
+ // This should be fixed, made cleaner
+ if ($name == 'column') {
+ $validators = $this->getValidators();
+ $validation = array_merge($validation, $validators);
+ }
+
+ $validation = array_flip($validation);
+
+ foreach ($element as $key => $value) {
+ if ( ! isset($validation[$value])) {
+ throw new IPF_ORM_Exception(sprintf('Invalid schema element named "' . $value . '"
+ at path "' . $path . '"'));
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Inflector
+{
+ public static function pluralize($word)
+ {
+ $plural = array('/(quiz)$/i' => '\1zes',
+ '/^(ox)$/i' => '\1en',
+ '/([m|l])ouse$/i' => '\1ice',
+ '/(matr|vert|ind)ix|ex$/i' => '\1ices',
+ '/(x|ch|ss|sh)$/i' => '\1es',
+ '/([^aeiouy]|qu)ies$/i' => '\1y',
+ '/([^aeiouy]|qu)y$/i' => '\1ies',
+ '/(hive)$/i' => '\1s',
+ '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
+ '/sis$/i' => 'ses',
+ '/([ti])um$/i' => '\1a',
+ '/(buffal|tomat)o$/i' => '\1oes',
+ '/(bu)s$/i' => '\1ses',
+ '/(alias|status)/i' => '\1es',
+ '/(octop|vir)us$/i' => '\1i',
+ '/(ax|test)is$/i' => '\1es',
+ '/s$/i' => 's',
+ '/$/' => 's');
+
+ $uncountable = array('equipment',
+ 'information',
+ 'rice',
+ 'money',
+ 'species',
+ 'series',
+ 'fish',
+ 'sheep');
+
+ $irregular = array('person' => 'people',
+ 'man' => 'men',
+ 'child' => 'children',
+ 'sex' => 'sexes',
+ 'move' => 'moves');
+
+ $lowercasedWord = strtolower($word);
+
+ foreach ($uncountable as $_uncountable) {
+ if(substr($lowercasedWord, (-1 * strlen($_uncountable))) == $_uncountable) {
+ return $word;
+ }
+ }
+
+ foreach ($irregular as $_plural=> $_singular){
+ if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
+ return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1) . substr($_singular,1), $word);
+ }
+ }
+
+ foreach ($plural as $rule => $replacement) {
+ if (preg_match($rule, $word)) {
+ return preg_replace($rule, $replacement, $word);
+ }
+ }
+
+ return false;
+ }
+
+ public static function singularize($word)
+ {
+ $singular = array('/(quiz)zes$/i' => '\\1',
+ '/(matr)ices$/i' => '\\1ix',
+ '/(vert|ind)ices$/i' => '\\1ex',
+ '/^(ox)en/i' => '\\1',
+ '/(alias|status)es$/i' => '\\1',
+ '/([octop|vir])i$/i' => '\\1us',
+ '/(cris|ax|test)es$/i' => '\\1is',
+ '/(shoe)s$/i' => '\\1',
+ '/(o)es$/i' => '\\1',
+ '/(bus)es$/i' => '\\1',
+ '/([m|l])ice$/i' => '\\1ouse',
+ '/(x|ch|ss|sh)es$/i' => '\\1',
+ '/(m)ovies$/i' => '\\1ovie',
+ '/(s)eries$/i' => '\\1eries',
+ '/([^aeiouy]|qu)ies$/i' => '\\1y',
+ '/([lr])ves$/i' => '\\1f',
+ '/(tive)s$/i' => '\\1',
+ '/(hive)s$/i' => '\\1',
+ '/([^f])ves$/i' => '\\1fe',
+ '/(^analy)ses$/i' => '\\1sis',
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\\1\\2sis',
+ '/([ti])a$/i' => '\\1um',
+ '/(n)ews$/i' => '\\1ews',
+ '/^(.{2,2})$/i' => '\\1',
+ '/s$/i' => '');
+
+ $uncountable = array('equipment',
+ 'information',
+ 'rice',
+ 'money',
+ 'species',
+ 'series',
+ 'fish',
+ 'sheep',
+ 'sms',
+ 'status',
+ 'access');
+
+ $irregular = array('person' => 'people',
+ 'man' => 'men',
+ 'child' => 'children',
+ 'sex' => 'sexes',
+ 'move' => 'moves');
+
+ $lowercasedWord = strtolower($word);
+ foreach ($uncountable as $_uncountable){
+ if(substr($lowercasedWord, ( -1 * strlen($_uncountable))) == $_uncountable){
+ return $word;
+ }
+ }
+
+ foreach ($irregular as $_singular => $_plural) {
+ if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
+ return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1).substr($_singular,1), $word);
+ }
+ }
+
+ foreach ($singular as $rule => $replacement) {
+ if (preg_match($rule, $word)) {
+ return preg_replace($rule, $replacement, $word);
+ }
+ }
+
+ return $word;
+ }
+
+ public static function tableize($word)
+ {
+ // Would prefer this but it breaks unit tests. Forces the table underscore pattern
+ // return self::pluralize(self::underscore($name));
+ return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
+ }
+
+ public static function classify($word)
+ {
+ return preg_replace_callback('~(_?)(_)([\w])~', array("IPF_ORM_Inflector", "classifyCallback"), ucfirst(strtolower($word)));
+ }
+
+ public static function classifyCallback($matches)
+ {
+ return $matches[1] . strtoupper($matches[3]);
+ }
+
+ public static function seemsUtf8($string)
+ {
+ for ($i = 0; $i < strlen($string); $i++) {
+ if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb
+ elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
+ elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
+ elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
+ elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
+ elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
+ else return false; # Does not match any model
+ for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
+ if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static function unaccent($string)
+ {
+ if ( ! preg_match('/[\x80-\xff]/', $string) ) {
+ return $string;
+ }
+
+ if (self::seemsUtf8($string)) {
+ $chars = array(
+ // Decompositions for Latin-1 Supplement
+ chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
+ chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
+ chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
+ chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
+ chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
+ chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
+ chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
+ chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
+ chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
+ chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
+ chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
+ chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
+ chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
+ chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
+ chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
+ chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
+ chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
+ chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
+ chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
+ chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
+ chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
+ chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
+ chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
+ chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
+ chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
+ chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
+ chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
+ chr(195).chr(191) => 'y',
+ // Decompositions for Latin Extended-A
+ chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
+ chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
+ chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
+ chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
+ chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
+ chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
+ chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
+ chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
+ chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
+ chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
+ chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
+ chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
+ chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
+ chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
+ chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
+ chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
+ chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
+ chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
+ chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
+ chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
+ chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
+ chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
+ chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
+ chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
+ chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
+ chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
+ chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
+ chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
+ chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
+ chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
+ chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
+ chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
+ chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
+ chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
+ chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
+ chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
+ chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
+ chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
+ chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
+ chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
+ chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
+ chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
+ chr(197).chr(148) => 'R', chr(197).chr(149) => 'r',
+ chr(197).chr(150) => 'R', chr(197).chr(151) => 'r',
+ chr(197).chr(152) => 'R', chr(197).chr(153) => 'r',
+ chr(197).chr(154) => 'S', chr(197).chr(155) => 's',
+ chr(197).chr(156) => 'S', chr(197).chr(157) => 's',
+ chr(197).chr(158) => 'S', chr(197).chr(159) => 's',
+ chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
+ chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
+ chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
+ chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
+ chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
+ chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
+ chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
+ chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
+ chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
+ chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
+ chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
+ chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
+ chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
+ chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
+ chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
+ chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
+ // Euro Sign
+ chr(226).chr(130).chr(172) => 'E',
+ // GBP (Pound) Sign
+ chr(194).chr(163) => '');
+
+ $string = strtr($string, $chars);
+ } else {
+ // Assume ISO-8859-1 if not UTF-8
+ $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
+ .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
+ .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
+ .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
+ .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
+ .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
+ .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
+ .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
+ .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
+ .chr(252).chr(253).chr(255);
+
+ $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
+
+ $string = strtr($string, $chars['in'], $chars['out']);
+ $doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
+ $doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
+ $string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
+ }
+
+ return $string;
+ }
+
+ public static function urlize($text)
+ {
+ // Remove all non url friendly characters with the unaccent function
+ $text = self::unaccent($text);
+
+ // Remove all none word characters
+ $text = preg_replace('/\W/', ' ', $text);
+
+ // More stripping. Replace spaces with dashes
+ $text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-',
+ preg_replace('/([a-z\d])([A-Z])/', '\1_\2',
+ preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2',
+ preg_replace('/::/', '/', $text)))));
+
+ return trim($text, '-');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Locator implements Countable, IteratorAggregate
+{
+ protected $_resources = array();
+ protected $_classPrefix = 'IPF_ORM_';
+ protected static $_instances = array();
+ public function __construct(array $defaults = null)
+ {
+ if (null !== $defaults) {
+ foreach ($defaults as $name => $resource) {
+ if ($resource instanceof IPF_ORM_Locator_Injectable) {
+ $resource->setLocator($this);
+ }
+ $this->_resources[$name] = $resource;
+ }
+ }
+ self::$_instances[] = $this;
+ }
+
+ public static function instance()
+ {
+ if (empty(self::$_instances)) {
+ $obj = new IPF_ORM_Locator();
+ }
+ return current(self::$_instances);
+ }
+
+ public function setClassPrefix($prefix)
+ {
+ $this->_classPrefix = $prefix;
+ }
+
+ public function getClassPrefix()
+ {
+ return $this->_classPrefix;
+ }
+
+ public function contains($name)
+ {
+ return isset($this->_resources[$name]);
+ }
+
+ public function bind($name, $value)
+ {
+ $this->_resources[$name] = $value;
+
+ return $this;
+ }
+
+ public function locate($name)
+ {
+ if (isset($this->_resources[$name])) {
+ return $this->_resources[$name];
+ } else {
+ $className = $name;
+
+ if ( ! class_exists($className)) {
+
+ $name = explode('.', $name);
+ $name = array_map('strtolower', $name);
+ $name = array_map('ucfirst', $name);
+ $name = implode('_', $name);
+
+ $className = $this->_classPrefix . $name;
+
+ if ( ! class_exists($className)) {
+ throw new IPF_ORM_Exception_Locator("Couldn't locate resource " . $className);
+ }
+ }
+
+ $this->_resources[$name] = new $className();
+
+ if ($this->_resources[$name] instanceof IPF_ORM_Locator_Injectable) {
+ $this->_resources[$name]->setLocator($this);
+ }
+
+ return $this->_resources[$name];
+ }
+
+ throw new IPF_ORM_Exception_Locator("Couldn't locate resource " . $name);
+ }
+
+ public function count()
+ {
+ return count($this->_resources);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_resources);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Locator_Injectable
+{
+ protected $_locator;
+ protected $_resources = array();
+
+ protected static $_null;
+
+ public function setLocator(IPF_ORM_Locator $locator)
+ {
+ $this->_locator = $locator;
+ return $this;
+ }
+
+ public function getLocator()
+ {
+ if ( ! isset($this->_locator)) {
+ $this->_locator = IPF_ORM_Locator::instance();
+
+ }
+ return $this->_locator;
+ }
+
+ public function locate($name)
+ {
+ if (isset($this->_resources[$name])) {
+ if (is_object($this->_resources[$name])) {
+ return $this->_resources[$name];
+ } else {
+ // get the name of the concrete implementation
+ $concreteImpl = $this->_resources[$name];
+
+ return $this->getLocator()->locate($concreteImpl);
+ }
+ } else {
+ return $this->getLocator()->locate($name);
+ }
+ }
+
+ public function bind($name, $resource)
+ {
+ $this->_resources[$name] = $resource;
+
+ return $this;
+ }
+
+ public static function initNullObject(IPF_ORM_Null $null)
+ {
+ self::$_null = $null;
+ }
+
+ public static function getNullObject()
+ {
+ return self::$_null;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Manager extends IPF_ORM_Configurable implements Countable, IteratorAggregate
+{
+ protected $_connections = array();
+ protected $_bound = array();
+ protected $_index = 0;
+ protected $_currIndex = 0;
+ protected $_queryRegistry;
+
+ private function __construct()
+ {
+ IPF_ORM_Locator_Injectable::initNullObject(new IPF_ORM_Null);
+ }
+
+ public function setDefaultAttributes()
+ {
+ static $init = false;
+ if ( ! $init) {
+ $init = true;
+ $attributes = array(
+ IPF_ORM::ATTR_CACHE => null,
+ IPF_ORM::ATTR_RESULT_CACHE => null,
+ IPF_ORM::ATTR_QUERY_CACHE => null,
+ IPF_ORM::ATTR_LOAD_REFERENCES => true,
+ IPF_ORM::ATTR_LISTENER => new IPF_ORM_EventListener(),
+ IPF_ORM::ATTR_RECORD_LISTENER => new IPF_ORM_Record_Listener(),
+ IPF_ORM::ATTR_THROW_EXCEPTIONS => true,
+ IPF_ORM::ATTR_VALIDATE => IPF_ORM::VALIDATE_NONE,
+ IPF_ORM::ATTR_QUERY_LIMIT => IPF_ORM::LIMIT_RECORDS,
+ IPF_ORM::ATTR_IDXNAME_FORMAT => "%s_idx",
+ IPF_ORM::ATTR_SEQNAME_FORMAT => "%s_seq",
+ IPF_ORM::ATTR_TBLNAME_FORMAT => "%s",
+ IPF_ORM::ATTR_QUOTE_IDENTIFIER => false,
+ IPF_ORM::ATTR_SEQCOL_NAME => 'id',
+ IPF_ORM::ATTR_PORTABILITY => IPF_ORM::PORTABILITY_ALL,
+ IPF_ORM::ATTR_EXPORT => IPF_ORM::EXPORT_ALL,
+ IPF_ORM::ATTR_DECIMAL_PLACES => 2,
+ IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE => 'ipf',
+ IPF_ORM::ATTR_AUTOLOAD_TABLE_CLASSES => false,
+ IPF_ORM::ATTR_USE_DQL_CALLBACKS => false,
+ );
+ foreach ($attributes as $attribute => $value) {
+ $old = $this->getAttribute($attribute);
+ if ($old === null) {
+ $this->setAttribute($attribute,$value);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static function getInstance()
+ {
+ static $instance;
+ if ( ! isset($instance)) {
+ $instance = new self();
+ }
+ return $instance;
+ }
+
+ public function getQueryRegistry()
+ {
+ if ( ! isset($this->_queryRegistry)) {
+ $this->_queryRegistry = new IPF_ORM_Query_Registry;
+ }
+ return $this->_queryRegistry;
+ }
+
+ public function setQueryRegistry(IPF_ORM_Query_Registry $registry)
+ {
+ $this->_queryRegistry = $registry;
+
+ return $this;
+ }
+
+ public static function connection($adapter = null, $name = null)
+ {
+ if ($adapter == null) {
+ return IPF_ORM_Manager::getInstance()->getCurrentConnection();
+ } else {
+ return IPF_ORM_Manager::getInstance()->openConnection($adapter, $name);
+ }
+ }
+
+ public function openConnection($adapter, $name = null, $setCurrent = true)
+ {
+ if (is_object($adapter)) {
+ if ( ! ($adapter instanceof PDO) && ! in_array('IPF_ORM_Adapter_Interface', class_implements($adapter))) {
+ throw new IPF_ORM_Exception("First argument should be an instance of PDO or implement IPF_ORM_Adapter_Interface");
+ }
+
+ $driverName = $adapter->getAttribute(IPF_ORM::ATTR_DRIVER_NAME);
+ } else if (is_array($adapter)) {
+ if ( ! isset($adapter[0])) {
+ throw new IPF_ORM_Exception('Empty data source name given.');
+ }
+ $e = explode(':', $adapter[0]);
+
+ if ($e[0] == 'uri') {
+ $e[0] = 'odbc';
+ }
+
+ $parts['dsn'] = $adapter[0];
+ $parts['scheme'] = $e[0];
+ $parts['user'] = (isset($adapter[1])) ? $adapter[1] : null;
+ $parts['pass'] = (isset($adapter[2])) ? $adapter[2] : null;
+ $driverName = $e[0];
+ $adapter = $parts;
+ } else {
+ $parts = $this->parseDsn($adapter);
+ $driverName = $parts['scheme'];
+ $adapter = $parts;
+ }
+
+ // Decode adapter information
+ if (is_array($adapter)) {
+ foreach ($adapter as $key => $value) {
+ $adapter[$key] = $value?urldecode($value):null;
+ }
+ }
+
+ // initialize the default attributes
+ $this->setDefaultAttributes();
+
+ if ($name !== null) {
+ $name = (string) $name;
+ if (isset($this->_connections[$name])) {
+ if ($setCurrent) {
+ $this->_currIndex = $name;
+ }
+ return $this->_connections[$name];
+ }
+ } else {
+ $name = $this->_index;
+ $this->_index++;
+ }
+
+ $drivers = array('mysql' => 'IPF_ORM_Connection_Mysql',
+ //'sqlite' => 'IPF_ORM_Connection_Sqlite',
+ //'pgsql' => 'IPF_ORM_Connection_Pgsql',
+ //'oci' => 'IPF_ORM_Connection_Oracle',
+ //'oci8' => 'IPF_ORM_Connection_Oracle',
+ //'oracle' => 'IPF_ORM_Connection_Oracle',
+ //'mssql' => 'IPF_ORM_Connection_Mssql',
+ //'dblib' => 'IPF_ORM_Connection_Mssql',
+ //'firebird' => 'IPF_ORM_Connection_Firebird',
+ //'informix' => 'IPF_ORM_Connection_Informix',
+ //'mock' => 'IPF_ORM_Connection_Mock'
+ );
+
+ if ( ! isset($drivers[$driverName])) {
+ throw new IPF_ORM_Exception('Unknown driver ' . $driverName);
+ }
+
+ $className = $drivers[$driverName];
+ $conn = new $className($this, $adapter);
+ $conn->setName($name);
+
+ $this->_connections[$name] = $conn;
+
+ if ($setCurrent) {
+ $this->_currIndex = $name;
+ }
+ return $this->_connections[$name];
+ }
+
+ public function parsePdoDsn($dsn)
+ {
+ $parts = array();
+
+ $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment', 'unix_socket');
+
+ foreach ($names as $name) {
+ if ( ! isset($parts[$name])) {
+ $parts[$name] = null;
+ }
+ }
+
+ $e = explode(':', $dsn);
+ $parts['scheme'] = $e[0];
+ $parts['dsn'] = $dsn;
+
+ $e = explode(';', $e[1]);
+ foreach ($e as $string) {
+ if ($string) {
+ $e2 = explode('=', $string);
+
+ if (isset($e2[0]) && isset($e2[1])) {
+ list($key, $value) = $e2;
+ $parts[$key] = $value;
+ }
+ }
+ }
+
+ return $parts;
+ }
+
+ protected function _buildDsnPartsArray($dsn)
+ {
+ // fix sqlite dsn so that it will parse correctly
+ $dsn = str_replace("////", "/", $dsn);
+ $dsn = str_replace("\\", "/", $dsn);
+ $dsn = preg_replace("/\/\/\/(.*):\//", "//$1:/", $dsn);
+
+ // silence any warnings
+ $parts = @parse_url($dsn);
+
+ $names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment', 'unix_socket');
+
+ foreach ($names as $name) {
+ if ( ! isset($parts[$name])) {
+ $parts[$name] = null;
+ }
+ }
+
+ if (count($parts) == 0 || ! isset($parts['scheme'])) {
+ throw new IPF_ORM_Exception('Could not parse dsn');
+ }
+
+ return $parts;
+ }
+
+ public function parseDsn($dsn)
+ {
+ $parts = $this->_buildDsnPartsArray($dsn);
+
+ switch ($parts['scheme']) {
+ case 'sqlite':
+ case 'sqlite2':
+ case 'sqlite3':
+ if (isset($parts['host']) && $parts['host'] == ':memory') {
+ $parts['database'] = ':memory:';
+ $parts['dsn'] = 'sqlite::memory:';
+ } else {
+ //fix windows dsn we have to add host: to path and set host to null
+ if (isset($parts['host'])) {
+ $parts['path'] = $parts['host'] . ":" . $parts["path"];
+ $parts['host'] = null;
+ }
+ $parts['database'] = $parts['path'];
+ $parts['dsn'] = $parts['scheme'] . ':' . $parts['path'];
+ }
+
+ break;
+
+ case 'mssql':
+ case 'dblib':
+ if ( ! isset($parts['path']) || $parts['path'] == '/') {
+ throw new IPF_ORM_Exception('No database available in data source name');
+ }
+ if (isset($parts['path'])) {
+ $parts['database'] = substr($parts['path'], 1);
+ }
+ if ( ! isset($parts['host'])) {
+ throw new IPF_ORM_Exception('No hostname set in data source name');
+ }
+
+ $parts['dsn'] = $parts['scheme'] . ':host='
+ . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port']:null) . ';dbname='
+ . $parts['database'];
+
+ break;
+
+ case 'mysql':
+ case 'informix':
+ case 'oci8':
+ case 'oci':
+ case 'firebird':
+ case 'pgsql':
+ case 'odbc':
+ case 'mock':
+ case 'oracle':
+ if ( ! isset($parts['path']) || $parts['path'] == '/') {
+ throw new IPF_ORM_Exception('No database available in data source name');
+ }
+ if (isset($parts['path'])) {
+ $parts['database'] = substr($parts['path'], 1);
+ }
+ if ( ! isset($parts['host'])) {
+ throw new IPF_ORM_Exception('No hostname set in data source name');
+ }
+
+ $parts['dsn'] = $parts['scheme'] . ':host='
+ . $parts['host'] . (isset($parts['port']) ? ';port=' . $parts['port']:null) . ';dbname='
+ . $parts['database'];
+
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown driver '.$parts['scheme']);
+ }
+
+ return $parts;
+ }
+
+ public function getConnection($name)
+ {
+ if ( ! isset($this->_connections[$name])) {
+ throw new IPF_ORM_Exception('Unknown connection: ' . $name);
+ }
+
+ return $this->_connections[$name];
+ }
+
+ public function getConnectionName(IPF_ORM_Connection $conn)
+ {
+ return array_search($conn, $this->_connections, true);
+ }
+
+ public function bindComponent($componentName, $connectionName)
+ {
+ $this->_bound[$componentName] = $connectionName;
+ }
+
+ public function getConnectionForComponent($componentName)
+ {
+ //IPF_ORM::autoload($componentName);
+
+ if (isset($this->_bound[$componentName])) {
+ return $this->getConnection($this->_bound[$componentName]);
+ }
+
+ return $this->getCurrentConnection();
+ }
+
+ public function hasConnectionForComponent($componentName = null)
+ {
+ return isset($this->_bound[$componentName]);
+ }
+
+ public function closeConnection(IPF_ORM_Connection $connection)
+ {
+ $connection->close();
+
+ $key = array_search($connection, $this->_connections, true);
+
+ if ($key !== false) {
+ unset($this->_connections[$key]);
+ }
+ $this->_currIndex = key($this->_connections);
+
+ unset($connection);
+ }
+
+ public function getConnections()
+ {
+ return $this->_connections;
+ }
+
+ public function setCurrentConnection($key)
+ {
+ $key = (string) $key;
+ if ( ! isset($this->_connections[$key])) {
+ throw new InvalidKeyException();
+ }
+ $this->_currIndex = $key;
+ }
+
+ public function contains($key)
+ {
+ return isset($this->_connections[$key]);
+ }
+
+ public function count()
+ {
+ return count($this->_connections);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_connections);
+ }
+
+ public function getCurrentConnection()
+ {
+ $i = $this->_currIndex;
+ if ( ! isset($this->_connections[$i])) {
+ throw new IPF_ORM_Exception('There is no open connection');
+ }
+ return $this->_connections[$i];
+ }
+
+ public function createDatabases($specifiedConnections = array())
+ {
+ if ( ! is_array($specifiedConnections)) {
+ $specifiedConnections = (array) $specifiedConnections;
+ }
+
+ $results = array();
+
+ foreach ($this as $name => $connection) {
+ if ( ! empty($specifiedConnections) && ! in_array($name, $specifiedConnections)) {
+ continue;
+ }
+
+ $results[$name] = $connection->createDatabase();
+ }
+
+ return $results;
+ }
+
+ public function dropDatabases($specifiedConnections = array())
+ {
+ if ( ! is_array($specifiedConnections)) {
+ $specifiedConnections = (array) $specifiedConnections;
+ }
+
+ $results = array();
+
+ foreach ($this as $name => $connection) {
+ if ( ! empty($specifiedConnections) && ! in_array($name, $specifiedConnections)) {
+ continue;
+ }
+
+ $results[$name] = $connection->dropDatabase();
+ }
+
+ return $results;
+ }
+
+ public function __toString()
+ {
+ $r[] = "<pre>";
+ $r[] = "IPF_ORM_Manager";
+ $r[] = "Connections : ".count($this->_connections);
+ $r[] = "</pre>";
+ return implode("\n",$r);
+ }
+}
--- /dev/null
+<?php
+
+final class IPF_ORM_Null
+{
+ public function exists()
+ {
+ return false;
+ }
+ public function __toString()
+ {
+ return '';
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+interface IPF_ORM_Overloadable {
+ public function __call($m, $a);
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Parser
+{
+ abstract public function loadData($array);
+ abstract public function dumpData($array, $path = null);
+
+ static public function getParser($type)
+ {
+ $class = 'IPF_ORM_Parser_'.ucfirst($type);
+ return new $class;
+ }
+
+ static public function load($path, $type = 'xml')
+ {
+ $parser = self::getParser($type);
+ return $parser->loadData($path);
+ }
+
+ static public function dump($array, $type = 'xml', $path = null)
+ {
+ $parser = self::getParser($type);
+ return $parser->dumpData($array, $path);
+ }
+
+ public function doLoad($path)
+ {
+ ob_start();
+ if ( ! file_exists($path)) {
+ $contents = $path;
+ $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'dparser_' . microtime();
+
+ file_put_contents($path, $contents);
+ }
+ include($path);
+ $contents = iconv("UTF-8", "UTF-8", ob_get_clean());
+ return $contents;
+ }
+
+ public function doDump($data, $path = null)
+ {
+ if ($path !== null) {
+ return file_put_contents($path, $data);
+ } else {
+ return $data;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Parser_Spyc_YamlNode
+{
+ var $parent;
+ var $id;
+ var $data;
+ var $indent;
+ var $children = false;
+ function __construct($nodeId)
+ {
+ $this->id = $nodeId;
+ }
+}
+
+class IPF_ORM_Parser_Spyc {
+
+ function YAMLLoad($input) {
+ $spyc = new IPF_ORM_Spyc;
+ return $spyc->load($input);
+ }
+
+ function YAMLDump($array,$indent = false,$wordwrap = false) {
+ $spyc = new IPF_ORM_Spyc;
+ return $spyc->dump($array,$indent,$wordwrap);
+ }
+
+ function load($input) {
+ if ( ! empty($input) && (strpos($input, "\n") === false)
+ && file_exists($input)) {
+ $yaml = file($input);
+ } else {
+ $yaml = explode("\n",$input);
+ }
+ $base = new IPF_ORM_Parser_Spyc_YamlNode (1);
+ $base->indent = 0;
+ $this->_lastIndent = 0;
+ $this->_lastNode = $base->id;
+ $this->_inBlock = false;
+ $this->_isInline = false;
+ $this->_nodeId = 2;
+
+ foreach ($yaml as $linenum => $line) {
+ $ifchk = trim($line);
+ if (preg_match('/^(\t)+(\w+)/', $line)) {
+ $err = 'ERROR: Line '. ($linenum + 1) .' in your input YAML begins'.
+ ' with a tab. YAML only recognizes spaces. Please reformat.';
+ die($err);
+ }
+
+ if ($this->_inBlock === false && empty($ifchk)) {
+ continue;
+ } elseif ($this->_inBlock == true && empty($ifchk)) {
+ $last =& $this->_allNodes[$this->_lastNode];
+ $last->data[key($last->data)] .= "\n";
+ } elseif ($ifchk{0} != '#' && substr($ifchk,0,3) != '---') {
+ $node = new IPF_ORM_Parser_Spyc_YamlNode ($this->_nodeId);
+ $this->_nodeId++;
+
+ $node->indent = $this->_getIndent($line);
+
+ // Check where the node lies in the hierarchy
+ if ($this->_lastIndent == $node->indent) {
+ // If we're in a block, add the text to the parent's data
+ if ($this->_inBlock === true) {
+ $parent =& $this->_allNodes[$this->_lastNode];
+ $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
+ } else {
+ // The current node's parent is the same as the previous node's
+ if (isset($this->_allNodes[$this->_lastNode])) {
+ $node->parent = $this->_allNodes[$this->_lastNode]->parent;
+ }
+ }
+ } elseif ($this->_lastIndent < $node->indent) {
+ if ($this->_inBlock === true) {
+ $parent =& $this->_allNodes[$this->_lastNode];
+ $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
+ } elseif ($this->_inBlock === false) {
+ // The current node's parent is the previous node
+ $node->parent = $this->_lastNode;
+
+ // If the value of the last node's data was > or | we need to
+ // start blocking i.e. taking in all lines as a text value until
+ // we drop our indent.
+ $parent =& $this->_allNodes[$node->parent];
+ $this->_allNodes[$node->parent]->children = true;
+ if (is_array($parent->data)) {
+ $chk = '';
+ if (isset ($parent->data[key($parent->data)]))
+ $chk = $parent->data[key($parent->data)];
+ if ($chk === '>') {
+ $this->_inBlock = true;
+ $this->_blockEnd = ' ';
+ $parent->data[key($parent->data)] =
+ str_replace('>','',$parent->data[key($parent->data)]);
+ $parent->data[key($parent->data)] .= trim($line).' ';
+ $this->_allNodes[$node->parent]->children = false;
+ $this->_lastIndent = $node->indent;
+ } elseif ($chk === '|') {
+ $this->_inBlock = true;
+ $this->_blockEnd = "\n";
+ $parent->data[key($parent->data)] =
+ str_replace('|','',$parent->data[key($parent->data)]);
+ $parent->data[key($parent->data)] .= trim($line)."\n";
+ $this->_allNodes[$node->parent]->children = false;
+ $this->_lastIndent = $node->indent;
+ }
+ }
+ }
+ } elseif ($this->_lastIndent > $node->indent) {
+ // Any block we had going is dead now
+ if ($this->_inBlock === true) {
+ $this->_inBlock = false;
+ if ($this->_blockEnd = "\n") {
+ $last =& $this->_allNodes[$this->_lastNode];
+ $last->data[key($last->data)] =
+ trim($last->data[key($last->data)]);
+ }
+ }
+
+ // We don't know the parent of the node so we have to find it
+ // foreach ($this->_allNodes as $n) {
+ foreach ($this->_indentSort[$node->indent] as $n) {
+ if ($n->indent == $node->indent) {
+ $node->parent = $n->parent;
+ }
+ }
+ }
+
+ if ($this->_inBlock === false) {
+ // Set these properties with information from our current node
+ $this->_lastIndent = $node->indent;
+ // Set the last node
+ $this->_lastNode = $node->id;
+ // Parse the YAML line and return its data
+ $node->data = $this->_parseLine($line);
+ // Add the node to the master list
+ $this->_allNodes[$node->id] = $node;
+ // Add a reference to the parent list
+ $this->_allParent[intval($node->parent)][] = $node->id;
+ // Add a reference to the node in an indent array
+ $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id];
+ // Add a reference to the node in a References array if this node
+ // has a YAML reference in it.
+ if (
+ ( (is_array($node->data)) &&
+ isset($node->data[key($node->data)]) &&
+ ( ! is_array($node->data[key($node->data)])) )
+ &&
+ ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)]))
+ ||
+ (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) )
+ ) {
+ $this->_haveRefs[] =& $this->_allNodes[$node->id];
+ } elseif (
+ ( (is_array($node->data)) &&
+ isset($node->data[key($node->data)]) &&
+ (is_array($node->data[key($node->data)])) )
+ ) {
+ // Incomplete reference making code. Ugly, needs cleaned up.
+ foreach ($node->data[key($node->data)] as $d) {
+ if ( !is_array($d) &&
+ ( (preg_match('/^&([^ ]+)/',$d))
+ ||
+ (preg_match('/^\*([^ ]+)/',$d)) )
+ ) {
+ $this->_haveRefs[] =& $this->_allNodes[$node->id];
+ }
+ }
+ }
+ }
+ }
+ }
+ unset($node);
+
+ // Here we travel through node-space and pick out references (& and *)
+ $this->_linkReferences();
+
+ // Build the PHP array out of node-space
+ $trunk = $this->_buildArray();
+ return $trunk;
+ }
+
+ function dump($array,$indent = false,$wordwrap = false) {
+ // Dumps to some very clean YAML. We'll have to add some more features
+ // and options soon. And better support for folding.
+
+ // New features and options.
+ if ($indent === false or !is_numeric($indent)) {
+ $this->_dumpIndent = 2;
+ } else {
+ $this->_dumpIndent = $indent;
+ }
+
+ if ($wordwrap === false or !is_numeric($wordwrap)) {
+ $this->_dumpWordWrap = 40;
+ } else {
+ $this->_dumpWordWrap = $wordwrap;
+ }
+
+ // New YAML document
+ $string = "---\n";
+
+ // Start at the base of the array and move through it.
+ foreach ($array as $key => $value) {
+ $string .= $this->_yamlize($key,$value,0);
+ }
+ return $string;
+ }
+
+ var $_haveRefs;
+ var $_allNodes;
+ var $_allParent;
+ var $_lastIndent;
+ var $_lastNode;
+ var $_inBlock;
+ var $_isInline;
+ var $_dumpIndent;
+ var $_dumpWordWrap;
+
+ var $_nodeId;
+
+ function _yamlize($key,$value,$indent) {
+ if (is_array($value)) {
+ // It has children. What to do?
+ // Make it the right kind of item
+ $string = $this->_dumpNode($key,NULL,$indent);
+ // Add the indent
+ $indent += $this->_dumpIndent;
+ // Yamlize the array
+ $string .= $this->_yamlizeArray($value,$indent);
+ } elseif ( ! is_array($value)) {
+ // It doesn't have children. Yip.
+ $string = $this->_dumpNode($key,$value,$indent);
+ }
+ return $string;
+ }
+
+ function _yamlizeArray($array,$indent) {
+ if (is_array($array)) {
+ $string = '';
+ foreach ($array as $key => $value) {
+ $string .= $this->_yamlize($key,$value,$indent);
+ }
+ return $string;
+ } else {
+ return false;
+ }
+ }
+
+ function _dumpNode($key,$value,$indent) {
+ // do some folding here, for blocks
+ if (strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false) {
+ $value = $this->_doLiteralBlock($value,$indent);
+ } else {
+ $value = $this->_doFolding($value,$indent);
+ }
+
+ if (is_bool($value)) {
+ $value = ($value) ? "true" : "false";
+ }
+
+ $spaces = str_repeat(' ',$indent);
+
+ if (is_int($key)) {
+ // It's a sequence
+ $string = $spaces.'- '.$value."\n";
+ } else {
+ // It's mapped
+ $string = $spaces.$key.': '.$value."\n";
+ }
+ return $string;
+ }
+
+ function _doLiteralBlock($value,$indent) {
+ $exploded = explode("\n",$value);
+ $newValue = '|';
+ $indent += $this->_dumpIndent;
+ $spaces = str_repeat(' ',$indent);
+ foreach ($exploded as $line) {
+ $newValue .= "\n" . $spaces . trim($line);
+ }
+ return $newValue;
+ }
+
+ function _doFolding($value,$indent) {
+ // Don't do anything if wordwrap is set to 0
+ if ($this->_dumpWordWrap === 0) {
+ return $value;
+ }
+
+ if (strlen($value) > $this->_dumpWordWrap) {
+ $indent += $this->_dumpIndent;
+ $indent = str_repeat(' ',$indent);
+ $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
+ $value = ">\n".$indent.$wrapped;
+ }
+ return $value;
+ }
+
+ function _getIndent($line) {
+ preg_match('/^\s{1,}/',$line,$match);
+ if ( ! empty($match[0])) {
+ $indent = substr_count($match[0],' ');
+ } else {
+ $indent = 0;
+ }
+ return $indent;
+ }
+
+ function _parseLine($line) {
+ $line = trim($line);
+
+ if(!preg_match("/\\\#/", $line)) {
+ $line = trim(preg_replace('/#.*$/', '', $line));
+ }
+
+ $array = array();
+
+ if (preg_match('/^-(.*):$/',$line)) {
+ // It's a mapped sequence
+ $key = trim(substr(substr($line,1),0,-1));
+ $array[$key] = '';
+ } elseif ($line[0] == '-' && substr($line,0,3) != '---') {
+ // It's a list item but not a new stream
+ if (strlen($line) > 1) {
+ $value = trim(substr($line,1));
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ $array[] = $value;
+ } else {
+ $array[] = array();
+ }
+ } elseif (preg_match('/^(.+):/',$line,$key)) {
+ // It's a key/value pair most likely
+ // If the key is in double quotes pull it out
+ if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
+ $value = trim(str_replace($matches[1],'',$line));
+ $key = $matches[2];
+ } else {
+ // Do some guesswork as to the key and the value
+ $explode = explode(':',$line);
+ $key = trim($explode[0]);
+ array_shift($explode);
+ $value = trim(implode(':',$explode));
+ }
+
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ if (empty($key)) {
+ $array[] = $value;
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ return $array;
+ }
+
+ function _toType($value) {
+ if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
+ $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
+ $value = preg_replace('/\\\\"/','"',$value);
+ } elseif (preg_match('/^\\[(.*)\\]$/',$value,$matches)) {
+ // Inline Sequence
+
+ // Take out strings sequences and mappings
+ $explode = empty($matches[1]) ? array() : $this->_inlineEscape($matches[1]);
+
+ // Propogate value array
+ $value = array();
+ foreach ($explode as $v) {
+ $value[] = $this->_toType($v);
+ }
+ } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) {
+ // It's a map
+ $array = explode(': ',$value);
+ $key = trim($array[0]);
+ array_shift($array);
+ $value = trim(implode(': ',$array));
+ $value = $this->_toType($value);
+ $value = array($key => $value);
+ } elseif (preg_match("/{(.+)}$/",$value,$matches)) {
+ // Inline Mapping
+
+ // Take out strings sequences and mappings
+ $explode = $this->_inlineEscape($matches[1]);
+
+ // Propogate value array
+ $array = array();
+ foreach ($explode as $v) {
+ $array = $array + $this->_toType($v);
+ }
+ $value = $array;
+ } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') {
+ $value = NULL;
+ } elseif (preg_match ('/^[0-9]+$/', $value)) {
+ // Cheeky change for compartibility with PHP < 4.2.0
+ $raw = $value;
+ $value = (int) $value;
+
+ if ((string) $value != (string) $raw) {
+ $value = (string) $raw;
+ }
+ } elseif (in_array(strtolower($value),
+ array('true', 'on', '+', 'yes', 'y'))) {
+ $value = true;
+ } elseif (in_array(strtolower($value),
+ array('false', 'off', '-', 'no', 'n'))) {
+ $value = false;
+ } elseif (is_numeric($value)) {
+ $raw = $value;
+ $value = (float) $value;
+
+ if ((string) $value != (string) $raw) {
+ $value = (string) $raw;
+ }
+ } else {
+ // Just a normal string, right?
+ $value = trim(preg_replace('/#(.+)$/','',$value));
+ }
+
+ return $value;
+ }
+
+ function _inlineEscape($inline) {
+ // There's gotta be a cleaner way to do this...
+ // While pure sequences seem to be nesting just fine,
+ // pure mappings and mappings with sequences inside can't go very
+ // deep. This needs to be fixed.
+
+ $saved_strings = array();
+
+ // Check for strings
+ $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
+ if (preg_match_all($regex,$inline,$strings)) {
+ $saved_strings = $strings[0];
+ $inline = preg_replace($regex,'YAMLString',$inline);
+ }
+ unset($regex);
+
+ // Check for sequences
+ if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) {
+ $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
+ $seqs = $seqs[0];
+ }
+
+ // Check for mappings
+ if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
+ $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
+ $maps = $maps[0];
+ }
+
+ $explode = explode(', ',$inline);
+
+
+ // Re-add the sequences
+ if ( ! empty($seqs)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLSeq') !== false) {
+ $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ // Re-add the mappings
+ if ( ! empty($maps)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLMap') !== false) {
+ $explode[$key] = str_replace('YAMLMap',$maps[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ // Re-add the strings
+ if ( ! empty($saved_strings)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ while (strpos($value,'YAMLString') !== false) {
+ $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$i],$value, 1);
+ ++$i;
+ $value = $explode[$key];
+ }
+ }
+ }
+
+ return $explode;
+ }
+
+ function _buildArray() {
+ $trunk = array();
+
+ if ( ! isset($this->_indentSort[0])) {
+ return $trunk;
+ }
+
+ foreach ($this->_indentSort[0] as $n) {
+ if (empty($n->parent)) {
+ $this->_nodeArrayizeData($n);
+ // Check for references and copy the needed data to complete them.
+ $this->_makeReferences($n);
+ // Merge our data with the big array we're building
+ $trunk = $this->_array_kmerge($trunk,$n->data);
+ }
+ }
+
+ return $trunk;
+ }
+
+ function _linkReferences() {
+ if (is_array($this->_haveRefs)) {
+ foreach ($this->_haveRefs as $node) {
+ if ( ! empty($node->data)) {
+ $key = key($node->data);
+ // If it's an array, don't check.
+ if (is_array($node->data[$key])) {
+ foreach ($node->data[$key] as $k => $v) {
+ $this->_linkRef($node,$key,$k,$v);
+ }
+ } else {
+ $this->_linkRef($node,$key);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
+ if (empty($k) && empty($v)) {
+ // Look for &refs
+ if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) {
+ // Flag the node so we know it's a reference
+ $this->_allNodes[$n->id]->ref = substr($matches[0],1);
+ $this->_allNodes[$n->id]->data[$key] =
+ substr($n->data[$key],strlen($matches[0])+1);
+ // Look for *refs
+ } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) {
+ $ref = substr($matches[0],1);
+ // Flag the node as having a reference
+ $this->_allNodes[$n->id]->refKey = $ref;
+ }
+ } elseif ( ! empty($k) && !empty($v)) {
+ if (preg_match('/^&([^ ]+)/',$v,$matches)) {
+ // Flag the node so we know it's a reference
+ $this->_allNodes[$n->id]->ref = substr($matches[0],1);
+ $this->_allNodes[$n->id]->data[$key][$k] =
+ substr($v,strlen($matches[0])+1);
+ // Look for *refs
+ } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) {
+ $ref = substr($matches[0],1);
+ // Flag the node as having a reference
+ $this->_allNodes[$n->id]->refKey = $ref;
+ }
+ }
+ }
+
+ function _gatherChildren($nid) {
+ $return = array();
+ $node =& $this->_allNodes[$nid];
+ if (is_array ($this->_allParent[$node->id])) {
+ foreach ($this->_allParent[$node->id] as $nodeZ) {
+ $z =& $this->_allNodes[$nodeZ];
+ // We found a child
+ $this->_nodeArrayizeData($z);
+ // Check for references
+ $this->_makeReferences($z);
+ // Merge with the big array we're returning
+ // The big array being all the data of the children of our parent node
+ $return = $this->_array_kmerge($return,$z->data);
+ }
+ }
+ return $return;
+ }
+
+ function _nodeArrayizeData(&$node) {
+ if (is_array($node->data) && $node->children == true) {
+ // This node has children, so we need to find them
+ $childs = $this->_gatherChildren($node->id);
+ // We've gathered all our children's data and are ready to use it
+ $key = key($node->data);
+ $key = empty($key) ? 0 : $key;
+ // If it's an array, add to it of course
+ if (isset ($node->data[$key])) {
+ if (is_array($node->data[$key])) {
+ $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs);
+ } else {
+ $node->data[$key] = $childs;
+ }
+ } else {
+ $node->data[$key] = $childs;
+ }
+ } elseif ( ! is_array($node->data) && $node->children == true) {
+ // Same as above, find the children of this node
+ $childs = $this->_gatherChildren($node->id);
+ $node->data = array();
+ $node->data[] = $childs;
+ }
+
+ // We edited $node by reference, so just return true
+ return true;
+ }
+
+ function _makeReferences(&$z) {
+ // It is a reference
+ if (isset($z->ref)) {
+ $key = key($z->data);
+ // Copy the data to this object for easy retrieval later
+ $this->ref[$z->ref] =& $z->data[$key];
+ // It has a reference
+ } elseif (isset($z->refKey)) {
+ if (isset($this->ref[$z->refKey])) {
+ $key = key($z->data);
+ // Copy the data from this object to make the node a real reference
+ $z->data[$key] =& $this->ref[$z->refKey];
+ }
+ }
+ return true;
+ }
+
+ function _array_kmerge($arr1,$arr2) {
+ if( ! is_array($arr1)) $arr1 = array();
+ if( ! is_array($arr2)) $arr2 = array();
+
+ $keys = array_merge(array_keys($arr1),array_keys($arr2));
+ $vals = array_merge(array_values($arr1),array_values($arr2));
+ $ret = array();
+ foreach($keys as $key) {
+ list($unused,$val) = each($vals);
+ if (isset($ret[$key]) and is_int($key)) $ret[] = $val; else $ret[$key] = $val;
+ }
+ return $ret;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Parser_Yml extends IPF_ORM_Parser
+{
+ public function dumpData($array, $path = null)
+ {
+ $spyc = new IPF_ORM_Parser_Spyc();
+ $data = $spyc->dump($array, false, false);
+ return $this->doDump($data, $path);
+ }
+
+ public function loadData($path)
+ {
+ $contents = $this->doLoad($path);
+ $spyc = new IPF_ORM_Parser_Spyc();
+ $array = $spyc->load($contents);
+ return $array;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query extends IPF_ORM_Query_Abstract implements Countable, Serializable
+{
+ protected static $_keywords = array('ALL',
+ 'AND',
+ 'ANY',
+ 'AS',
+ 'ASC',
+ 'AVG',
+ 'BETWEEN',
+ 'BIT_LENGTH',
+ 'BY',
+ 'CHARACTER_LENGTH',
+ 'CHAR_LENGTH',
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP',
+ 'DELETE',
+ 'DESC',
+ 'DISTINCT',
+ 'EMPTY',
+ 'EXISTS',
+ 'FALSE',
+ 'FETCH',
+ 'FROM',
+ 'GROUP',
+ 'HAVING',
+ 'IN',
+ 'INDEXBY',
+ 'INNER',
+ 'IS',
+ 'JOIN',
+ 'LEFT',
+ 'LIKE',
+ 'LOWER',
+ 'MEMBER',
+ 'MOD',
+ 'NEW',
+ 'NOT',
+ 'NULL',
+ 'OBJECT',
+ 'OF',
+ 'OR',
+ 'ORDER',
+ 'OUTER',
+ 'POSITION',
+ 'SELECT',
+ 'SOME',
+ 'TRIM',
+ 'TRUE',
+ 'UNKNOWN',
+ 'UPDATE',
+ 'WHERE');
+
+ protected $_subqueryAliases = array();
+ protected $_aggregateAliasMap = array();
+ protected $_pendingAggregates = array();
+ protected $_needsSubquery = false;
+ protected $_isSubquery;
+ protected $_neededTables = array();
+ protected $_pendingSubqueries = array();
+ protected $_pendingFields = array();
+ protected $_parsers = array();
+ protected $_pendingJoinConditions = array();
+ protected $_expressionMap = array();
+ protected $_sql;
+
+ public static function create($conn = null)
+ {
+ return new IPF_ORM_Query($conn);
+ }
+
+ public function reset()
+ {
+ $this->_pendingJoinConditions = array();
+ $this->_pendingSubqueries = array();
+ $this->_pendingFields = array();
+ $this->_neededTables = array();
+ $this->_expressionMap = array();
+ $this->_subqueryAliases = array();
+ $this->_needsSubquery = false;
+ $this->_isLimitSubqueryUsed = false;
+ }
+
+ public function createSubquery()
+ {
+ $class = get_class($this);
+ $obj = new $class();
+
+ // copy the aliases to the subquery
+ $obj->copyAliases($this);
+
+ // this prevents the 'id' being selected, re ticket #307
+ $obj->isSubquery(true);
+
+ return $obj;
+ }
+
+ protected function _addPendingJoinCondition($componentAlias, $joinCondition)
+ {
+ $this->_pendingJoins[$componentAlias] = $joinCondition;
+ }
+
+ public function addEnumParam($key, $table = null, $column = null)
+ {
+ $array = (isset($table) || isset($column)) ? array($table, $column) : array();
+
+ if ($key === '?') {
+ $this->_enumParams[] = $array;
+ } else {
+ $this->_enumParams[$key] = $array;
+ }
+ }
+
+ public function getEnumParams()
+ {
+ return $this->_enumParams;
+ }
+
+ public function getDql()
+ {
+ $q = '';
+ $q .= ( ! empty($this->_dqlParts['select']))? 'SELECT ' . implode(', ', $this->_dqlParts['select']) : '';
+ $q .= ( ! empty($this->_dqlParts['from']))? ' FROM ' . implode(' ', $this->_dqlParts['from']) : '';
+ $q .= ( ! empty($this->_dqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_dqlParts['where']) : '';
+ $q .= ( ! empty($this->_dqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : '';
+ $q .= ( ! empty($this->_dqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_dqlParts['having']) : '';
+ $q .= ( ! empty($this->_dqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : '';
+ $q .= ( ! empty($this->_dqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_dqlParts['limit']) : '';
+ $q .= ( ! empty($this->_dqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_dqlParts['offset']) : '';
+
+ return $q;
+ }
+
+
+ public function fetchArray($params = array()) {
+ return $this->execute($params, IPF_ORM::HYDRATE_ARRAY);
+ }
+
+ public function fetchOne($params = array(), $hydrationMode = null)
+ {
+ $collection = $this->execute($params, $hydrationMode);
+
+ if (count($collection) === 0) {
+ return false;
+ }
+
+ if ($collection instanceof IPF_ORM_Collection) {
+ return $collection->getFirst();
+ } else if (is_array($collection)) {
+ return array_shift($collection);
+ }
+
+ return false;
+ }
+
+ public function isSubquery($bool = null)
+ {
+ if ($bool === null) {
+ return $this->_isSubquery;
+ }
+
+ $this->_isSubquery = (bool) $bool;
+ return $this;
+ }
+
+ public function getAggregateAlias($dqlAlias)
+ {
+ return $this->getSqlAggregateAlias($dqlAlias);
+ }
+
+ public function getSqlAggregateAlias($dqlAlias)
+ {
+ if (isset($this->_aggregateAliasMap[$dqlAlias])) {
+ // mark the expression as used
+ $this->_expressionMap[$dqlAlias][1] = true;
+
+ return $this->_aggregateAliasMap[$dqlAlias];
+ } else if ( ! empty($this->_pendingAggregates)) {
+ $this->processPendingAggregates();
+
+ return $this->getSqlAggregateAlias($dqlAlias);
+ } else {
+ throw new IPF_ORM_Exception('Unknown aggregate alias: ' . $dqlAlias);
+ }
+ }
+
+ public function getDqlPart($queryPart)
+ {
+ if ( ! isset($this->_dqlParts[$queryPart])) {
+ throw new IPF_ORM_Exception('Unknown query part ' . $queryPart);
+ }
+
+ return $this->_dqlParts[$queryPart];
+ }
+
+ public function contains($dql)
+ {
+ return stripos($this->getDql(), $dql) === false ? false : true;
+ }
+
+ public function processPendingFields($componentAlias)
+ {
+ $tableAlias = $this->getTableAlias($componentAlias);
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+ if ( ! isset($this->_pendingFields[$componentAlias])) {
+ if ($this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) {
+ if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) {
+ throw new IPF_ORM_Exception("The root class of the query (alias $componentAlias) "
+ . " must have at least one field selected.");
+ }
+ }
+ return;
+ }
+
+ // At this point we know the component is FETCHED (either it's the base class of
+ // the query (FROM xyz) or its a "fetch join").
+
+ // Check that the parent join (if there is one), is a "fetch join", too.
+ if (isset($this->_queryComponents[$componentAlias]['parent'])) {
+ $parentAlias = $this->_queryComponents[$componentAlias]['parent'];
+ if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias])
+ && $this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) {
+ throw new IPF_ORM_Exception("The left side of the join between "
+ . "the aliases '$parentAlias' and '$componentAlias' must have at least"
+ . " the primary key field(s) selected.");
+ }
+ }
+
+ $fields = $this->_pendingFields[$componentAlias];
+
+ // check for wildcards
+ if (in_array('*', $fields)) {
+ $fields = $table->getFieldNames();
+ } else {
+ // only auto-add the primary key fields if this query object is not
+ // a subquery of another query object
+ if ( ! $this->_isSubquery || $this->_hydrator->getHydrationMode() === IPF_ORM::HYDRATE_NONE) {
+ $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields));
+ }
+ }
+
+ $sql = array();
+ foreach ($fields as $fieldName) {
+ $columnName = $table->getColumnName($fieldName);
+ if (($owner = $table->getColumnOwner($columnName)) !== null &&
+ $owner !== $table->getComponentName()) {
+
+ $parent = $this->_conn->getTable($owner);
+ $columnName = $parent->getColumnName($fieldName);
+ $parentAlias = $this->getTableAlias($componentAlias . '.' . $parent->getComponentName());
+ $sql[] = $this->_conn->quoteIdentifier($parentAlias . '.' . $columnName)
+ . ' AS '
+ . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
+ } else {
+ $columnName = $table->getColumnName($fieldName);
+ $sql[] = $this->_conn->quoteIdentifier($tableAlias . '.' . $columnName)
+ . ' AS '
+ . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
+ }
+ }
+
+ $this->_neededTables[] = $tableAlias;
+
+ return implode(', ', $sql);
+ }
+
+ public function parseSelectField($field)
+ {
+ $terms = explode('.', $field);
+
+ if (isset($terms[1])) {
+ $componentAlias = $terms[0];
+ $field = $terms[1];
+ } else {
+ reset($this->_queryComponents);
+ $componentAlias = key($this->_queryComponents);
+ $fields = $terms[0];
+ }
+
+ $tableAlias = $this->getTableAlias($componentAlias);
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+
+ // check for wildcards
+ if ($field === '*') {
+ $sql = array();
+
+ foreach ($table->getColumnNames() as $field) {
+ $sql[] = $this->parseSelectField($componentAlias . '.' . $field);
+ }
+
+ return implode(', ', $sql);
+ } else {
+ $name = $table->getColumnName($field);
+
+ $this->_neededTables[] = $tableAlias;
+
+ return $this->_conn->quoteIdentifier($tableAlias . '.' . $name)
+ . ' AS '
+ . $this->_conn->quoteIdentifier($tableAlias . '__' . $name);
+ }
+ }
+
+ public function getExpressionOwner($expr)
+ {
+ if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') {
+ preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches);
+
+ $match = current($matches);
+
+ if (isset($match[0])) {
+ $terms = explode('.', $match[0]);
+
+ return $terms[0];
+ }
+ }
+ return $this->getRootAlias();
+
+ }
+
+ public function parseSelect($dql)
+ {
+ $refs = $this->_tokenizer->sqlExplode($dql, ',');
+
+ $pos = strpos(trim($refs[0]), ' ');
+ $first = substr($refs[0], 0, $pos);
+
+ // check for DISTINCT keyword
+ if ($first === 'DISTINCT') {
+ $this->_sqlParts['distinct'] = true;
+
+ $refs[0] = substr($refs[0], ++$pos);
+ }
+
+ $parsedComponents = array();
+
+ foreach ($refs as $reference) {
+ $reference = trim($reference);
+
+ if (empty($reference)) {
+ continue;
+ }
+
+ $terms = $this->_tokenizer->sqlExplode($reference, ' ');
+
+ $pos = strpos($terms[0], '(');
+
+ if (count($terms) > 1 || $pos !== false) {
+ $expression = array_shift($terms);
+ $alias = array_pop($terms);
+
+ if ( ! $alias) {
+ $alias = substr($expression, 0, $pos);
+ }
+
+ $componentAlias = $this->getExpressionOwner($expression);
+ $expression = $this->parseClause($expression);
+
+ $tableAlias = $this->getTableAlias($componentAlias);
+
+ $index = count($this->_aggregateAliasMap);
+
+ $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
+
+ $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;
+
+ $this->_aggregateAliasMap[$alias] = $sqlAlias;
+ $this->_expressionMap[$alias][0] = $expression;
+
+ $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
+
+ $this->_neededTables[] = $tableAlias;
+ } else {
+ $e = explode('.', $terms[0]);
+
+ if (isset($e[1])) {
+ $componentAlias = $e[0];
+ $field = $e[1];
+ } else {
+ reset($this->_queryComponents);
+ $componentAlias = key($this->_queryComponents);
+ $field = $e[0];
+ }
+
+ $this->_pendingFields[$componentAlias][] = $field;
+ }
+ }
+ }
+
+ public function parseClause($clause)
+ {
+ $clause = trim($clause);
+
+ if (is_numeric($clause)) {
+ return $clause;
+ }
+
+ $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<='));
+
+ $str = '';
+ foreach ($terms as $term) {
+ $pos = strpos($term[0], '(');
+
+ if ($pos !== false) {
+ $name = substr($term[0], 0, $pos);
+ $term[0] = $this->parseFunctionExpression($term[0]);
+ } else {
+ if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") {
+
+ if (strpos($term[0], '.') !== false) {
+ if ( ! is_numeric($term[0])) {
+ $e = explode('.', $term[0]);
+
+ $field = array_pop($e);
+
+ if ($this->getType() === IPF_ORM_Query::SELECT) {
+ $componentAlias = implode('.', $e);
+
+ if (empty($componentAlias)) {
+ $componentAlias = $this->getRootAlias();
+ }
+
+ $this->load($componentAlias);
+
+ // check the existence of the component alias
+ if ( ! isset($this->_queryComponents[$componentAlias])) {
+ throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
+ }
+
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+ $def = $table->getDefinitionOf($field);
+
+ // get the actual field name from alias
+ $field = $table->getColumnName($field);
+
+ // check column existence
+ if ( ! $def) {
+ throw new IPF_ORM_Exception('Unknown column ' . $field);
+ }
+
+ if (isset($def['owner'])) {
+ $componentAlias = $componentAlias . '.' . $def['owner'];
+ }
+
+ $tableAlias = $this->getTableAlias($componentAlias);
+
+ // build sql expression
+ $term[0] = $this->_conn->quoteIdentifier($tableAlias)
+ . '.'
+ . $this->_conn->quoteIdentifier($field);
+ } else {
+ // build sql expression
+ $field = $this->getRoot()->getColumnName($field);
+ $term[0] = $this->_conn->quoteIdentifier($field);
+ }
+ }
+ } else {
+ if ( ! empty($term[0]) &&
+ ! in_array(strtoupper($term[0]), self::$_keywords) &&
+ ! is_numeric($term[0])) {
+
+ $componentAlias = $this->getRootAlias();
+
+ $found = false;
+
+ if ($componentAlias !== false &&
+ $componentAlias !== null) {
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+ // check column existence
+ if ($table->hasField($term[0])) {
+ $found = true;
+
+ $def = $table->getDefinitionOf($term[0]);
+
+ // get the actual column name from field name
+ $term[0] = $table->getColumnName($term[0]);
+
+
+ if (isset($def['owner'])) {
+ $componentAlias = $componentAlias . '.' . $def['owner'];
+ }
+
+ $tableAlias = $this->getTableAlias($componentAlias);
+
+ if ($this->getType() === IPF_ORM_Query::SELECT) {
+ // build sql expression
+ $term[0] = $this->_conn->quoteIdentifier($tableAlias)
+ . '.'
+ . $this->_conn->quoteIdentifier($term[0]);
+ } else {
+ // build sql expression
+ $term[0] = $this->_conn->quoteIdentifier($term[0]);
+ }
+ } else {
+ $found = false;
+ }
+ }
+
+ if ( ! $found) {
+ $term[0] = $this->getSqlAggregateAlias($term[0]);
+ }
+ }
+ }
+ }
+ }
+
+ $str .= $term[0] . $term[1];
+ }
+ return $str;
+ }
+
+ public function parseIdentifierReference($expr)
+ {
+ }
+
+ public function parseFunctionExpression($expr)
+ {
+ $pos = strpos($expr, '(');
+
+ $name = substr($expr, 0, $pos);
+
+ if ($name === '') {
+ return $this->parseSubquery($expr);
+ }
+
+ $argStr = substr($expr, ($pos + 1), -1);
+
+ $args = array();
+ // parse args
+
+ foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) {
+ $args[] = $this->parseClause($arg);
+ }
+
+ // convert DQL function to its RDBMS specific equivalent
+ try {
+ $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
+ } catch (IPF_ORM_Exception $e) {
+ throw new IPF_ORM_Exception('Unknown function ' . $name . '.');
+ }
+
+ return $expr;
+ }
+
+ public function parseSubquery($subquery)
+ {
+ $trimmed = trim($this->_tokenizer->bracketTrim($subquery));
+
+ // check for possible subqueries
+ if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
+ // parse subquery
+ $trimmed = $this->createSubquery()->parseDqlQuery($trimmed)->getQuery();
+ } else {
+ // parse normal clause
+ $trimmed = $this->parseClause($trimmed);
+ }
+
+ return '(' . $trimmed . ')';
+ }
+
+ public function processPendingSubqueries()
+ {
+ foreach ($this->_pendingSubqueries as $value) {
+ list($dql, $alias) = $value;
+
+ $subquery = $this->createSubquery();
+
+ $sql = $subquery->parseDqlQuery($dql, false)->getQuery();
+
+ reset($this->_queryComponents);
+ $componentAlias = key($this->_queryComponents);
+ $tableAlias = $this->getTableAlias($componentAlias);
+
+ $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap);
+
+ $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias);
+
+ $this->_aggregateAliasMap[$alias] = $sqlAlias;
+ $this->_queryComponents[$componentAlias]['agg'][] = $alias;
+ }
+ $this->_pendingSubqueries = array();
+ }
+
+ public function processPendingAggregates()
+ {
+ // iterate trhough all aggregates
+ foreach ($this->_pendingAggregates as $aggregate) {
+ list ($expression, $components, $alias) = $aggregate;
+
+ $tableAliases = array();
+
+ // iterate through the component references within the aggregate function
+ if ( ! empty ($components)) {
+ foreach ($components as $component) {
+
+ if (is_numeric($component)) {
+ continue;
+ }
+
+ $e = explode('.', $component);
+
+ $field = array_pop($e);
+ $componentAlias = implode('.', $e);
+
+ // check the existence of the component alias
+ if ( ! isset($this->_queryComponents[$componentAlias])) {
+ throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
+ }
+
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+ $field = $table->getColumnName($field);
+
+ // check column existence
+ if ( ! $table->hasColumn($field)) {
+ throw new IPF_ORM_Exception('Unknown column ' . $field);
+ }
+
+ $sqlTableAlias = $this->getSqlTableAlias($componentAlias);
+
+ $tableAliases[$sqlTableAlias] = true;
+
+ // build sql expression
+
+ $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field);
+ $expression = str_replace($component, $identifier, $expression);
+ }
+ }
+
+ if (count($tableAliases) !== 1) {
+ $componentAlias = reset($this->_tableAliasMap);
+ $tableAlias = key($this->_tableAliasMap);
+ }
+
+ $index = count($this->_aggregateAliasMap);
+ $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
+
+ $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;
+
+ $this->_aggregateAliasMap[$alias] = $sqlAlias;
+ $this->_expressionMap[$alias][0] = $expression;
+
+ $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
+
+ $this->_neededTables[] = $tableAlias;
+ }
+ // reset the state
+ $this->_pendingAggregates = array();
+ }
+
+ protected function _buildSqlQueryBase()
+ {
+ switch ($this->_type) {
+ case self::DELETE:
+ $q = 'DELETE FROM ';
+ break;
+ case self::UPDATE:
+ $q = 'UPDATE ';
+ break;
+ case self::SELECT:
+ $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : '';
+ $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM ';
+ break;
+ }
+ return $q;
+ }
+
+ protected function _buildSqlFromPart()
+ {
+ $q = '';
+ foreach ($this->_sqlParts['from'] as $k => $part) {
+ if ($k === 0) {
+ $q .= $part;
+ continue;
+ }
+
+ // preserve LEFT JOINs only if needed
+ // Check if it's JOIN, if not add a comma separator instead of space
+ if (!preg_match('/\bJOIN\b/i', $part) && !isset($this->_pendingJoinConditions[$k])) {
+ $q .= ', ' . $part;
+ } else {
+ $e = explode(' ', $part);
+
+ if (substr($part, 0, 9) === 'LEFT JOIN') {
+ $aliases = array_merge($this->_subqueryAliases,
+ array_keys($this->_neededTables));
+
+ if ( ! in_array($e[3], $aliases) &&
+ ! in_array($e[2], $aliases) &&
+
+ ! empty($this->_pendingFields)) {
+ continue;
+ }
+
+ }
+
+ if (isset($this->_pendingJoinConditions[$k])) {
+ $parser = new IPF_ORM_JoinCondition($this, $this->_tokenizer);
+
+ if (strpos($part, ' ON ') !== false) {
+ $part .= ' AND ';
+ } else {
+ $part .= ' ON ';
+ }
+ $part .= $parser->parse($this->_pendingJoinConditions[$k]);
+
+ unset($this->_pendingJoinConditions[$k]);
+ }
+
+ $tableAlias = trim($e[3], '"');
+ $componentAlias = $this->getComponentAlias($tableAlias);
+
+ $string = $this->getInheritanceCondition($componentAlias);
+
+ if ($string) {
+ $q .= ' ' . $part . ' AND ' . $string;
+ } else {
+ $q .= ' ' . $part;
+ }
+ }
+
+ $this->_sqlParts['from'][$k] = $part;
+ }
+ return $q;
+ }
+
+ public function getSqlQuery($params = array())
+ {
+ if ($this->_state !== self::STATE_DIRTY) {
+ return $this->_sql;
+ }
+
+ // reset the state
+ if ( ! $this->isSubquery()) {
+ $this->_queryComponents = array();
+ $this->_pendingAggregates = array();
+ $this->_aggregateAliasMap = array();
+ }
+ $this->reset();
+
+ // invoke the preQuery hook
+ $this->_preQuery();
+
+ // process the DQL parts => generate the SQL parts.
+ // this will also populate the $_queryComponents.
+ foreach ($this->_dqlParts as $queryPartName => $queryParts) {
+ $this->_processDqlQueryPart($queryPartName, $queryParts);
+ }
+ $this->_state = self::STATE_CLEAN;
+
+ $params = $this->convertEnums($params);
+
+ // Proceed with the generated SQL
+
+ if (empty($this->_sqlParts['from'])) {
+ return false;
+ }
+
+ $needsSubQuery = false;
+ $subquery = '';
+ $map = reset($this->_queryComponents);
+ $table = $map['table'];
+ $rootAlias = key($this->_queryComponents);
+
+ if ( ! empty($this->_sqlParts['limit']) && $this->_needsSubquery &&
+ $table->getAttribute(IPF_ORM::ATTR_QUERY_LIMIT) == IPF_ORM::LIMIT_RECORDS) {
+ $this->_isLimitSubqueryUsed = true;
+ $needsSubQuery = true;
+ }
+
+ $sql = array();
+ if ( ! empty($this->_pendingFields)) {
+ foreach ($this->_queryComponents as $alias => $map) {
+ $fieldSql = $this->processPendingFields($alias);
+ if ( ! empty($fieldSql)) {
+ $sql[] = $fieldSql;
+ }
+ }
+ }
+ if ( ! empty($sql)) {
+ array_unshift($this->_sqlParts['select'], implode(', ', $sql));
+ }
+
+ $this->_pendingFields = array();
+
+ // build the basic query
+ $q = $this->_buildSqlQueryBase();
+ $q .= $this->_buildSqlFromPart();
+
+ if ( ! empty($this->_sqlParts['set'])) {
+ $q .= ' SET ' . implode(', ', $this->_sqlParts['set']);
+ }
+
+ $string = $this->getInheritanceCondition($this->getRootAlias());
+
+ // apply inheritance to WHERE part
+ if ( ! empty($string)) {
+ if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') {
+ $this->_sqlParts['where'][] = $string;
+ } else {
+ $this->_sqlParts['where'][] = '(' . $string . ')';
+ }
+ }
+
+ $modifyLimit = true;
+ if ( ! empty($this->_sqlParts['limit']) || ! empty($this->_sqlParts['offset'])) {
+ if ($needsSubQuery) {
+ $subquery = $this->getLimitSubquery();
+ // what about composite keys?
+ $idColumnName = $table->getColumnName($table->getIdentifier());
+ switch (strtolower($this->_conn->getDriverName())) {
+ case 'mysql':
+ // mysql doesn't support LIMIT in subqueries
+ $list = $this->_conn->execute($subquery, $params)->fetchAll(IPF_ORM::FETCH_COLUMN);
+ $subquery = implode(', ', array_map(array($this->_conn, 'quote'), $list));
+ break;
+ case 'pgsql':
+ // pgsql needs special nested LIMIT subquery
+ $subquery = 'SELECT ipf_orm_subquery_alias.' . $idColumnName . ' FROM (' . $subquery . ') AS ipf_orm_subquery_alias';
+ break;
+ }
+
+ $field = $this->getSqlTableAlias($rootAlias) . '.' . $idColumnName;
+
+ // only append the subquery if it actually contains something
+ if ($subquery !== '') {
+ array_unshift($this->_sqlParts['where'], $this->_conn->quoteIdentifier($field) . ' IN (' . $subquery . ')');
+ }
+
+ $modifyLimit = false;
+ }
+ }
+
+ $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : '';
+ $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
+ $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']): '';
+ $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
+
+ if ($modifyLimit) {
+ $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']);
+ }
+
+ // return to the previous state
+ if ( ! empty($string)) {
+ array_pop($this->_sqlParts['where']);
+ }
+ if ($needsSubQuery) {
+ array_shift($this->_sqlParts['where']);
+ }
+ $this->_sql = $q;
+
+ return $q;
+ }
+
+ public function getLimitSubquery()
+ {
+ $map = reset($this->_queryComponents);
+ $table = $map['table'];
+ $componentAlias = key($this->_queryComponents);
+
+ // get short alias
+ $alias = $this->getTableAlias($componentAlias);
+ // what about composite keys?
+ $primaryKey = $alias . '.' . $table->getColumnName($table->getIdentifier());
+
+ // initialize the base of the subquery
+ $subquery = 'SELECT DISTINCT ' . $this->_conn->quoteIdentifier($primaryKey);
+
+ $driverName = $this->_conn->getAttribute(IPF_ORM::ATTR_DRIVER_NAME);
+
+ // pgsql needs the order by fields to be preserved in select clause
+ if ($driverName == 'pgsql') {
+ foreach ($this->_sqlParts['orderby'] as $part) {
+ $part = trim($part);
+ $e = $this->_tokenizer->bracketExplode($part, ' ');
+ $part = trim($e[0]);
+
+ if (strpos($part, '.') === false) {
+ continue;
+ }
+
+ // don't add functions
+ if (strpos($part, '(') !== false) {
+ continue;
+ }
+
+ // don't add primarykey column (its already in the select clause)
+ if ($part !== $primaryKey) {
+ $subquery .= ', ' . $part;
+ }
+ }
+ }
+
+ if ($driverName == 'mysql' || $driverName == 'pgsql') {
+ foreach ($this->_expressionMap as $dqlAlias => $expr) {
+ if (isset($expr[1])) {
+ $subquery .= ', ' . $expr[0] . ' AS ' . $this->_aggregateAliasMap[$dqlAlias];
+ }
+ }
+ }
+
+ $subquery .= ' FROM';
+
+ foreach ($this->_sqlParts['from'] as $part) {
+ // preserve LEFT JOINs only if needed
+ if (substr($part, 0, 9) === 'LEFT JOIN') {
+ $e = explode(' ', $part);
+
+ if (empty($this->_sqlParts['orderby']) && empty($this->_sqlParts['where'])) {
+ continue;
+ }
+ }
+
+ $subquery .= ' ' . $part;
+ }
+
+ // all conditions must be preserved in subquery
+ $subquery .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : '';
+ $subquery .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
+ $subquery .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']) : '';
+
+ $subquery .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
+
+ // add driver specific limit clause
+ $subquery = $this->_conn->modifyLimitSubquery($table, $subquery, $this->_sqlParts['limit'], $this->_sqlParts['offset']);
+
+ $parts = $this->_tokenizer->quoteExplode($subquery, ' ', "'", "'");
+
+ foreach ($parts as $k => $part) {
+ if (strpos($part, ' ') !== false) {
+ continue;
+ }
+
+ $part = str_replace(array('"', "'", '`'), "", $part);
+
+ if ($this->hasSqlTableAlias($part)) {
+ $parts[$k] = $this->_conn->quoteIdentifier($this->generateNewSqlTableAlias($part));
+ continue;
+ }
+
+ if (strpos($part, '.') === false) {
+ continue;
+ }
+
+ preg_match_all("/[a-zA-Z0-9_]+\.[a-z0-9_]+/i", $part, $m);
+
+ foreach ($m[0] as $match) {
+ $e = explode('.', $match);
+
+ // Rebuild the original part without the newly generate alias and with quoting reapplied
+ $e2 = array();
+ foreach ($e as $k2 => $v2) {
+ $e2[$k2] = $this->_conn->quoteIdentifier($v2);
+ }
+ $match = implode('.', $e2);
+
+ // Generate new table alias
+ $e[0] = $this->generateNewSqlTableAlias($e[0]);
+
+ // Requote the part with the newly generated alias
+ foreach ($e as $k2 => $v2) {
+ $e[$k2] = $this->_conn->quoteIdentifier($v2);
+ }
+
+ $replace = implode('.' , $e);
+
+ // Replace the original part with the new part with new sql table alias
+ $parts[$k] = str_replace($match, $replace, $parts[$k]);
+ }
+ }
+
+ if ($driverName == 'mysql' || $driverName == 'pgsql') {
+ foreach ($parts as $k => $part) {
+ if (strpos($part, "'") !== false) {
+ continue;
+ }
+ if (strpos($part, '__') == false) {
+ continue;
+ }
+
+ preg_match_all("/[a-zA-Z0-9_]+\_\_[a-z0-9_]+/i", $part, $m);
+
+ foreach ($m[0] as $match) {
+ $e = explode('__', $match);
+ $e[0] = $this->generateNewTableAlias($e[0]);
+
+ $parts[$k] = str_replace($match, implode('__', $e), $parts[$k]);
+ }
+ }
+ }
+
+ $subquery = implode(' ', $parts);
+ return $subquery;
+ }
+
+ public function parseDqlQuery($query, $clear = true)
+ {
+ if ($clear) {
+ $this->clear();
+ }
+
+ $query = trim($query);
+ $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query));
+ $query = str_replace("\n", ' ', $query);
+
+ $parts = $this->_tokenizer->tokenizeQuery($query);
+
+ foreach ($parts as $partName => $subParts) {
+ $subParts = trim($subParts);
+ $partName = strtolower($partName);
+ switch ($partName) {
+ case 'create':
+ $this->_type = self::CREATE;
+ break;
+ case 'insert':
+ $this->_type = self::INSERT;
+ break;
+ case 'delete':
+ $this->_type = self::DELETE;
+ break;
+ case 'select':
+ $this->_type = self::SELECT;
+ $this->_addDqlQueryPart($partName, $subParts);
+ break;
+ case 'update':
+ $this->_type = self::UPDATE;
+ $partName = 'from';
+ case 'from':
+ $this->_addDqlQueryPart($partName, $subParts);
+ break;
+ case 'set':
+ $this->_addDqlQueryPart($partName, $subParts, true);
+ break;
+ case 'group':
+ case 'order':
+ $partName .= 'by';
+ case 'where':
+ case 'having':
+ case 'limit':
+ case 'offset':
+ $this->_addDqlQueryPart($partName, $subParts);
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ public function load($path, $loadFields = true)
+ {
+ if (isset($this->_queryComponents[$path])) {
+ return $this->_queryComponents[$path];
+ }
+
+ $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY ');
+
+ $mapWith = null;
+ if (count($e) > 1) {
+ $mapWith = trim($e[1]);
+
+ $path = $e[0];
+ }
+
+ // parse custom join conditions
+ $e = explode(' ON ', $path);
+
+ $joinCondition = '';
+
+ if (count($e) > 1) {
+ $joinCondition = $e[1];
+ $overrideJoin = true;
+ $path = $e[0];
+ } else {
+ $e = explode(' WITH ', $path);
+
+ if (count($e) > 1) {
+ $joinCondition = $e[1];
+ $path = $e[0];
+ }
+ $overrideJoin = false;
+ }
+
+ $tmp = explode(' ', $path);
+ $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
+
+ $e = preg_split("/[.:]/", $tmp[0], -1);
+
+ $fullPath = $tmp[0];
+ $prevPath = '';
+ $fullLength = strlen($fullPath);
+
+ if (isset($this->_queryComponents[$e[0]])) {
+ $table = $this->_queryComponents[$e[0]]['table'];
+ $componentAlias = $e[0];
+
+ $prevPath = $parent = array_shift($e);
+ }
+
+ foreach ($e as $key => $name) {
+ // get length of the previous path
+ $length = strlen($prevPath);
+
+ // build the current component path
+ $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name;
+
+ $delimeter = substr($fullPath, $length, 1);
+
+ // if an alias is not given use the current path as an alias identifier
+ if (strlen($prevPath) === $fullLength && isset($originalAlias)) {
+ $componentAlias = $originalAlias;
+ } else {
+ $componentAlias = $prevPath;
+ }
+
+ // if the current alias already exists, skip it
+ if (isset($this->_queryComponents[$componentAlias])) {
+ throw new IPF_ORM_Exception("Duplicate alias '$componentAlias' in query.");
+ }
+
+ if ( ! isset($table)) {
+ // process the root of the path
+
+ $table = $this->loadRoot($name, $componentAlias);
+ } else {
+ $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN ';
+
+ $relation = $table->getRelation($name);
+ $localTable = $table;
+
+ $table = $relation->getTable();
+ $this->_queryComponents[$componentAlias] = array('table' => $table,
+ 'parent' => $parent,
+ 'relation' => $relation,
+ 'map' => null);
+ if ( ! $relation->isOneToOne()) {
+ $this->_needsSubquery = true;
+ }
+
+ $localAlias = $this->getTableAlias($parent, $table->getTableName());
+ $foreignAlias = $this->getTableAlias($componentAlias, $relation->getTable()->getTableName());
+
+ $foreignSql = $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
+ . ' '
+ . $this->_conn->quoteIdentifier($foreignAlias);
+
+ $map = $relation->getTable()->inheritanceMap;
+
+ if ( ! $loadFields || ! empty($map) || $joinCondition) {
+ $this->_subqueryAliases[] = $foreignAlias;
+ }
+
+ if ($relation instanceof IPF_ORM_Relation_Association) {
+ $asf = $relation->getAssociationTable();
+
+ $assocTableName = $asf->getTableName();
+
+ if ( ! $loadFields || ! empty($map) || $joinCondition) {
+ $this->_subqueryAliases[] = $assocTableName;
+ }
+
+ $assocPath = $prevPath . '.' . $asf->getComponentName();
+
+ $this->_queryComponents[$assocPath] = array('parent' => $prevPath, 'relation' => $relation, 'table' => $asf);
+
+ $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName());
+
+ $queryPart = $join . $assocTableName . ' ' . $assocAlias;
+
+ $queryPart .= ' ON ' . $localAlias
+ . '.'
+ . $localTable->getColumnName($localTable->getIdentifier()) // what about composite keys?
+ . ' = '
+ . $assocAlias . '.' . $relation->getLocal();
+
+ if ($relation->isEqual()) {
+ // equal nest relation needs additional condition
+ $queryPart .= ' OR ' . $localAlias
+ . '.'
+ . $table->getColumnName($table->getIdentifier())
+ . ' = '
+ . $assocAlias . '.' . $relation->getForeign();
+ }
+
+ $this->_sqlParts['from'][] = $queryPart;
+
+ $queryPart = $join . $foreignSql;
+
+ if ( ! $overrideJoin) {
+ $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias);
+ }
+ } else {
+ $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join);
+ }
+
+ $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias);
+
+ $this->_sqlParts['from'][$componentAlias] = $queryPart;
+ if ( ! empty($joinCondition)) {
+ $this->_pendingJoinConditions[$componentAlias] = $joinCondition;
+ }
+ }
+ if ($loadFields) {
+
+ $restoreState = false;
+ // load fields if necessary
+ if ($loadFields && empty($this->_dqlParts['select'])) {
+ $this->_pendingFields[$componentAlias] = array('*');
+ }
+ }
+ $parent = $prevPath;
+ }
+
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+ return $this->buildIndexBy($componentAlias, $mapWith);
+ }
+
+ protected function buildSimpleRelationSql(IPF_ORM_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join)
+ {
+ $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
+ . ' '
+ . $this->_conn->quoteIdentifier($foreignAlias);
+
+ if ( ! $overrideJoin) {
+ $queryPart .= ' ON '
+ . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocal())
+ . ' = '
+ . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeign());
+ }
+
+ return $queryPart;
+ }
+
+ protected function buildIndexBy($componentAlias, $mapWith = null)
+ {
+ $table = $this->_queryComponents[$componentAlias]['table'];
+
+ $indexBy = null;
+
+ if (isset($mapWith)) {
+ $terms = explode('.', $mapWith);
+
+ if (isset($terms[1])) {
+ $indexBy = $terms[1];
+ }
+ } elseif ($table->getBoundQueryPart('indexBy') !== null) {
+ $indexBy = $table->getBoundQueryPart('indexBy');
+ }
+
+ if ($indexBy !== null) {
+ if ( ! $table->hasColumn($table->getColumnName($indexBy))) {
+ throw new IPF_ORM_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist.");
+ }
+
+ $this->_queryComponents[$componentAlias]['map'] = $indexBy;
+ }
+
+ return $this->_queryComponents[$componentAlias];
+ }
+
+
+ protected function buildAssociativeRelationSql(IPF_ORM_Relation $relation, $assocAlias, $foreignAlias, $localAlias)
+ {
+ $table = $relation->getTable();
+
+ $queryPart = ' ON ';
+
+ if ($relation->isEqual()) {
+ $queryPart .= '(';
+ }
+
+ $localIdentifier = $table->getColumnName($table->getIdentifier());
+
+ $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
+ . ' = '
+ . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeign());
+
+ if ($relation->isEqual()) {
+ $queryPart .= ' OR '
+ . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
+ . ' = '
+ . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocal())
+ . ') AND '
+ . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
+ . ' != '
+ . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier);
+ }
+
+ return $queryPart;
+ }
+
+ public function loadRoot($name, $componentAlias)
+ {
+ // get the connection for the component
+ $manager = IPF_ORM_Manager::getInstance();
+ if ($manager->hasConnectionForComponent($name)) {
+ $this->_conn = $manager->getConnectionForComponent($name);
+ }
+
+ $table = $this->_conn->getTable($name);
+ $tableName = $table->getTableName();
+
+ // get the short alias for this table
+ $tableAlias = $this->getTableAlias($componentAlias, $tableName);
+ // quote table name
+ $queryPart = $this->_conn->quoteIdentifier($tableName);
+
+ if ($this->_type === self::SELECT) {
+ $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias);
+ }
+
+ $this->_tableAliasMap[$tableAlias] = $componentAlias;
+
+ $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias);
+
+ $this->_sqlParts['from'][] = $queryPart;
+
+ $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null);
+
+ return $table;
+ }
+
+ public function buildInheritanceJoinSql($name, $componentAlias)
+ {
+ // get the connection for the component
+ $manager = IPF_ORM_Manager::getInstance();
+ if ($manager->hasConnectionForComponent($name)) {
+ $this->_conn = $manager->getConnectionForComponent($name);
+ }
+
+ $table = $this->_conn->getTable($name);
+ $tableName = $table->getTableName();
+
+ // get the short alias for this table
+ $tableAlias = $this->getTableAlias($componentAlias, $tableName);
+
+ $queryPart = '';
+
+ foreach ($table->getOption('joinedParents') as $parent) {
+ $parentTable = $this->_conn->getTable($parent);
+
+ $parentAlias = $componentAlias . '.' . $parent;
+
+ // get the short alias for the parent table
+ $parentTableAlias = $this->getTableAlias($parentAlias, $parentTable->getTableName());
+
+ $queryPart .= ' LEFT JOIN ' . $this->_conn->quoteIdentifier($parentTable->getTableName())
+ . ' ' . $this->_conn->quoteIdentifier($parentTableAlias) . ' ON ';
+
+ //IPF_ORM::dump($table->getIdentifier());
+ foreach ((array) $table->getIdentifier() as $identifier) {
+ $column = $table->getColumnName($identifier);
+
+ $queryPart .= $this->_conn->quoteIdentifier($tableAlias)
+ . '.' . $this->_conn->quoteIdentifier($column)
+ . ' = ' . $this->_conn->quoteIdentifier($parentTableAlias)
+ . '.' . $this->_conn->quoteIdentifier($column);
+ }
+ }
+
+ return $queryPart;
+ }
+
+ public function getCountQuery()
+ {
+ // triggers dql parsing/processing
+ $this->getQuery(); // this is ugly
+
+ // initialize temporary variables
+ $where = $this->_sqlParts['where'];
+ $having = $this->_sqlParts['having'];
+ $groupby = $this->_sqlParts['groupby'];
+ $map = reset($this->_queryComponents);
+ $componentAlias = key($this->_queryComponents);
+ $tableAlias = $this->getTableAlias($componentAlias);
+ $table = $map['table'];
+ $idColumnNames = $table->getIdentifierColumnNames();
+
+ // build the query base
+ $q = 'SELECT COUNT(DISTINCT ' . $this->_conn->quoteIdentifier($tableAlias)
+ . '.' . implode(
+ ' || ' . $this->_conn->quoteIdentifier($tableAlias) . '.',
+ $this->_conn->quoteMultipleIdentifier($idColumnNames)
+ ) . ') AS num_results';
+
+ foreach ($this->_sqlParts['select'] as $field) {
+ if (strpos($field, '(') !== false) {
+ $q .= ', ' . $field;
+ }
+ }
+
+ $q .= ' FROM ' . $this->_buildSqlFromPart();
+
+ // append column aggregation inheritance (if needed)
+ $string = $this->getInheritanceCondition($this->getRootAlias());
+
+ if ( ! empty($string)) {
+ $where[] = $string;
+ }
+
+ // append conditions
+ $q .= ( ! empty($where)) ? ' WHERE ' . implode(' AND ', $where) : '';
+
+ if ( ! empty($groupby)) {
+ // Maintain existing groupby
+ $q .= ' GROUP BY ' . implode(', ', $groupby);
+ } else {
+ // Default groupby to primary identifier. Database defaults to this internally
+ // This is required for situations where the user has aggregate functions in the select part
+ // Without the groupby it fails
+ $q .= ' GROUP BY ' . $this->_conn->quoteIdentifier($tableAlias)
+ . '.' . implode(
+ ', ' . $this->_conn->quoteIdentifier($tableAlias) . '.',
+ $this->_conn->quoteMultipleIdentifier($idColumnNames)
+ );
+ }
+
+ $q .= ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having): '';
+
+ return $q;
+ }
+
+ public function count($params = array())
+ {
+ $q = $this->getCountQuery();
+
+ if ( ! is_array($params)) {
+ $params = array($params);
+ }
+
+ $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $params);
+
+ $params = $this->convertEnums($params);
+
+ $results = $this->getConnection()->fetchAll($q, $params);
+
+ if (count($results) > 1) {
+ $count = count($results);
+ } else {
+ if (isset($results[0])) {
+ $results[0] = array_change_key_case($results[0], CASE_LOWER);
+ $count = $results[0]['num_results'];
+ } else {
+ $count = 0;
+ }
+ }
+
+ return (int) $count;
+ }
+
+ public function query($query, $params = array(), $hydrationMode = null)
+ {
+ $this->parseDqlQuery($query);
+ return $this->execute($params, $hydrationMode);
+ }
+
+ public function copy(IPF_ORM_Query $query = null)
+ {
+ if ( ! $query) {
+ $query = $this;
+ }
+
+ $new = clone $query;
+
+ return $new;
+ }
+
+ public function __clone()
+ {
+ $this->_parsers = array();
+ }
+
+ public function free()
+ {
+ $this->reset();
+ $this->_parsers = array();
+ $this->_dqlParts = array();
+ $this->_enumParams = array();
+ }
+
+ public function serialize()
+ {
+ $vars = get_object_vars($this);
+ }
+
+ public function unserialize($serialized)
+ {
+ }
+}
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Query_Abstract
+{
+ const SELECT = 0;
+ const DELETE = 1;
+ const UPDATE = 2;
+ const INSERT = 3;
+ const CREATE = 4;
+
+ const STATE_CLEAN = 1;
+ const STATE_DIRTY = 2;
+ const STATE_DIRECT = 3;
+ const STATE_LOCKED = 4;
+
+ protected $_tableAliasMap = array();
+ protected $_view;
+ protected $_state = IPF_ORM_Query::STATE_CLEAN;
+ protected $_params = array('join' => array(),
+ 'where' => array(),
+ 'set' => array(),
+ 'having' => array());
+
+ protected $_resultCache;
+ protected $_expireResultCache = false;
+ protected $_resultCacheTTL;
+
+ protected $_queryCache;
+ protected $_expireQueryCache = false;
+ protected $_queryCacheTTL;
+
+ protected $_conn;
+
+ protected $_sqlParts = array(
+ 'select' => array(),
+ 'distinct' => false,
+ 'forUpdate' => false,
+ 'from' => array(),
+ 'set' => array(),
+ 'join' => array(),
+ 'where' => array(),
+ 'groupby' => array(),
+ 'having' => array(),
+ 'orderby' => array(),
+ 'limit' => false,
+ 'offset' => false,
+ );
+
+ protected $_dqlParts = array(
+ 'from' => array(),
+ 'select' => array(),
+ 'forUpdate' => false,
+ 'set' => array(),
+ 'join' => array(),
+ 'where' => array(),
+ 'groupby' => array(),
+ 'having' => array(),
+ 'orderby' => array(),
+ 'limit' => array(),
+ 'offset' => array(),
+ );
+
+ protected $_queryComponents = array();
+ protected $_type = self::SELECT;
+ protected $_hydrator;
+ protected $_tokenizer;
+ protected $_parser;
+ protected $_tableAliasSeeds = array();
+ protected $_options = array(
+ 'fetchMode' => IPF_ORM::FETCH_RECORD
+ );
+ protected $_enumParams = array();
+
+ protected $_isLimitSubqueryUsed = false;
+ protected $_pendingSetParams = array();
+ protected $_components;
+ protected $_preQueried = false;
+
+ public function __construct(IPF_ORM_Connection $connection = null,
+ IPF_ORM_Hydrator_Abstract $hydrator = null)
+ {
+ if ($connection === null) {
+ $connection = IPF_ORM_Manager::getInstance()->getCurrentConnection();
+ }
+ if ($hydrator === null) {
+ $hydrator = new IPF_ORM_Hydrator();
+ }
+ $this->_conn = $connection;
+ $this->_hydrator = $hydrator;
+ $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
+ $this->_resultCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN);
+ $this->_queryCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN);
+ }
+
+ public function setOption($name, $value)
+ {
+ if ( ! isset($this->_options[$name])) {
+ throw new IPF_ORM_Exception('Unknown option ' . $name);
+ }
+ $this->_options[$name] = $value;
+ }
+
+ public function hasTableAlias($sqlTableAlias)
+ {
+ return $this->hasSqlTableAlias($sqlTableAlias);
+ }
+
+ public function hasSqlTableAlias($sqlTableAlias)
+ {
+ return (isset($this->_tableAliasMap[$sqlTableAlias]));
+ }
+
+ public function getTableAliases()
+ {
+ return $this->getTableAliasMap();
+ }
+
+ public function getTableAliasMap()
+ {
+ return $this->_tableAliasMap;
+ }
+
+ public function getQueryPart($part)
+ {
+ return $this->getSqlQueryPart($part);
+ }
+
+ public function getSqlQueryPart($part)
+ {
+ if ( ! isset($this->_sqlParts[$part])) {
+ throw new IPF_ORM_Exception('Unknown SQL query part ' . $part);
+ }
+ return $this->_sqlParts[$part];
+ }
+
+ public function setQueryPart($name, $part)
+ {
+ return $this->setSqlQueryPart($name, $part);
+ }
+
+ public function setSqlQueryPart($name, $part)
+ {
+ if ( ! isset($this->_sqlParts[$name])) {
+ throw new IPF_ORM_Exception('Unknown query part ' . $name);
+ }
+
+ if ($name !== 'limit' && $name !== 'offset') {
+ if (is_array($part)) {
+ $this->_sqlParts[$name] = $part;
+ } else {
+ $this->_sqlParts[$name] = array($part);
+ }
+ } else {
+ $this->_sqlParts[$name] = $part;
+ }
+
+ return $this;
+ }
+
+ public function addQueryPart($name, $part)
+ {
+ return $this->addSqlQueryPart($name, $part);
+ }
+
+ public function addSqlQueryPart($name, $part)
+ {
+ if ( ! isset($this->_sqlParts[$name])) {
+ throw new IPF_ORM_Exception('Unknown query part ' . $name);
+ }
+ if (is_array($part)) {
+ $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part);
+ } else {
+ $this->_sqlParts[$name][] = $part;
+ }
+ return $this;
+ }
+
+ public function removeQueryPart($name)
+ {
+ return $this->removeSqlQueryPart($name);
+ }
+
+ public function removeSqlQueryPart($name)
+ {
+ if ( ! isset($this->_sqlParts[$name])) {
+ throw new IPF_ORM_Exception('Unknown query part ' . $name);
+ }
+
+ if ($name == 'limit' || $name == 'offset') {
+ $this->_sqlParts[$name] = false;
+ } else {
+ $this->_sqlParts[$name] = array();
+ }
+
+ return $this;
+ }
+
+ public function removeDqlQueryPart($name)
+ {
+ if ( ! isset($this->_dqlParts[$name])) {
+ throw new IPF_ORM_Exception('Unknown query part ' . $name);
+ }
+
+ if ($name == 'limit' || $name == 'offset') {
+ $this->_dqlParts[$name] = false;
+ } else {
+ $this->_dqlParts[$name] = array();
+ }
+
+ return $this;
+ }
+
+ public function getParams($params = array())
+ {
+ return array_merge($this->_params['join'], $this->_params['set'], $this->_params['where'], $this->_params['having'], $params);
+ }
+
+ public function setParams(array $params = array()) {
+ $this->_params = $params;
+ }
+
+ public function setView(IPF_ORM_View $view)
+ {
+ $this->_view = $view;
+ }
+
+ public function getView()
+ {
+ return $this->_view;
+ }
+
+ public function isLimitSubqueryUsed()
+ {
+ return $this->_isLimitSubqueryUsed;
+ }
+
+ public function convertEnums($params)
+ {
+ $table = $this->getRoot();
+
+ // $position tracks the position of the parameter, to ensure we're converting
+ // the right parameter value when simple ? placeholders are used.
+ // This only works because SET is only allowed in update statements and it's
+ // the first place where parameters can occur.. see issue #935
+ $position = 0;
+ foreach ($this->_pendingSetParams as $fieldName => $value) {
+ $e = explode('.', $fieldName);
+ $fieldName = isset($e[1]) ? $e[1]:$e[0];
+ if ($table->getTypeOf($fieldName) == 'enum') {
+ $value = $value === '?' ? $position : $value;
+ $this->addEnumParam($value, $table, $fieldName);
+ }
+ ++$position;
+ }
+ $this->_pendingSetParams = array();
+
+ foreach ($this->_enumParams as $key => $values) {
+ if (isset($params[$key])) {
+ if ( ! empty($values)) {
+ $params[$key] = $values[0]->enumIndex($values[1], $params[$key]);
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ public function getInheritanceCondition($componentAlias)
+ {
+ $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap;
+
+ // No inheritance map so lets just return
+ if (empty($map)) {
+ return;
+ }
+
+ $tableAlias = $this->getSqlTableAlias($componentAlias);
+
+ if ($this->_type !== IPF_ORM_Query::SELECT) {
+ $tableAlias = '';
+ } else {
+ $tableAlias .= '.';
+ }
+
+ $field = key($map);
+ $value = current($map);
+ $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
+
+ return $identifier . ' = ' . $this->_conn->quote($value);;
+ }
+
+ public function getTableAlias($componentAlias, $tableName = null)
+ {
+ return $this->getSqlTableAlias($componentAlias, $tableName);
+ }
+
+ public function getSqlTableAlias($componentAlias, $tableName = null)
+ {
+ $alias = array_search($componentAlias, $this->_tableAliasMap);
+
+ if ($alias !== false) {
+ return $alias;
+ }
+
+ if ($tableName === null) {
+ throw new IPF_ORM_Exception("Couldn't get short alias for " . $componentAlias);
+ }
+
+ return $this->generateTableAlias($componentAlias, $tableName);
+ }
+
+ public function generateNewTableAlias($oldAlias)
+ {
+ return $this->generateNewSqlTableAlias($oldAlias);
+ }
+
+ public function generateNewSqlTableAlias($oldAlias)
+ {
+ if (isset($this->_tableAliasMap[$oldAlias])) {
+ // generate a new alias
+ $name = substr($oldAlias, 0, 1);
+ $i = ((int) substr($oldAlias, 1));
+
+ if ($i == 0) {
+ $i = 1;
+ }
+
+ $newIndex = ($this->_tableAliasSeeds[$name] + $i);
+
+ return $name . $newIndex;
+ }
+
+ return $oldAlias;
+ }
+
+ public function getTableAliasSeed($sqlTableAlias)
+ {
+ return $this->getSqlTableAliasSeed($sqlTableAlias);
+ }
+
+ public function getSqlTableAliasSeed($sqlTableAlias)
+ {
+ if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) {
+ return 0;
+ }
+ return $this->_tableAliasSeeds[$sqlTableAlias];
+ }
+
+ public function hasAliasDeclaration($componentAlias)
+ {
+ return isset($this->_queryComponents[$componentAlias]);
+ }
+
+ public function getAliasDeclaration($componentAlias)
+ {
+ return $this->getQueryComponent($componentAlias);
+ }
+
+ public function getQueryComponent($componentAlias)
+ {
+ if ( ! isset($this->_queryComponents[$componentAlias])) {
+ throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
+ }
+
+ return $this->_queryComponents[$componentAlias];
+ }
+
+ public function copyAliases(IPF_ORM_Query_Abstract $query)
+ {
+ $this->_tableAliasMap = $query->_tableAliasMap;
+ $this->_queryComponents = $query->_queryComponents;
+ $this->_tableAliasSeeds = $query->_tableAliasSeeds;
+ return $this;
+ }
+
+ public function getRootAlias()
+ {
+ if ( ! $this->_queryComponents) {
+ $this->getSql();
+ }
+ reset($this->_queryComponents);
+
+ return key($this->_queryComponents);
+ }
+
+ public function getRootDeclaration()
+ {
+ $map = reset($this->_queryComponents);
+ return $map;
+ }
+
+ public function getRoot()
+ {
+ $map = reset($this->_queryComponents);
+
+ if ( ! isset($map['table'])) {
+ throw new IPF_ORM_Exception('Root component not initialized.');
+ }
+
+ return $map['table'];
+ }
+
+ public function generateTableAlias($componentAlias, $tableName)
+ {
+ return $this->generateSqlTableAlias($componentAlias, $tableName);
+ }
+
+ public function generateSqlTableAlias($componentAlias, $tableName)
+ {
+ preg_match('/([^_])/', $tableName, $matches);
+ $char = strtolower($matches[0]);
+
+ $alias = $char;
+
+ if ( ! isset($this->_tableAliasSeeds[$alias])) {
+ $this->_tableAliasSeeds[$alias] = 1;
+ }
+
+ while (isset($this->_tableAliasMap[$alias])) {
+ if ( ! isset($this->_tableAliasSeeds[$alias])) {
+ $this->_tableAliasSeeds[$alias] = 1;
+ }
+ $alias = $char . ++$this->_tableAliasSeeds[$alias];
+ }
+
+ $this->_tableAliasMap[$alias] = $componentAlias;
+
+ return $alias;
+ }
+
+ public function getComponentAlias($sqlTableAlias)
+ {
+ if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) {
+ throw new IPF_ORM_Exception('Unknown table alias ' . $sqlTableAlias);
+ }
+ return $this->_tableAliasMap[$sqlTableAlias];
+ }
+
+ protected function _execute($params)
+ {
+ $params = $this->_conn->convertBooleans($params);
+
+ if ( ! $this->_view) {
+ if ($this->_queryCache || $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) {
+ $queryCacheDriver = $this->getQueryCacheDriver();
+ // calculate hash for dql query
+ $dql = $this->getDql();
+ $hash = md5($dql . 'IPF_ORM_QUERY_CACHE_SALT');
+ $cached = $queryCacheDriver->fetch($hash);
+ if ($cached) {
+ $query = $this->_constructQueryFromCache($cached);
+ } else {
+ $query = $this->getSqlQuery($params);
+ $serializedQuery = $this->getCachedForm($query);
+ $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan());
+ }
+ } else {
+ $query = $this->getSqlQuery($params);
+ }
+ $params = $this->convertEnums($params);
+ } else {
+ $query = $this->_view->getSelectSql();
+ }
+
+ if ($this->isLimitSubqueryUsed() &&
+ $this->_conn->getAttribute(IPF_ORM::ATTR_DRIVER_NAME) !== 'mysql') {
+ $params = array_merge($params, $params);
+ }
+
+ if ($this->_type !== self::SELECT) {
+ return $this->_conn->exec($query, $params);
+ }
+
+ $stmt = $this->_conn->execute($query, $params);
+ return $stmt;
+ }
+
+ public function execute($params = array(), $hydrationMode = null)
+ {
+ $this->_preQuery();
+
+ if ($hydrationMode !== null) {
+ $this->_hydrator->setHydrationMode($hydrationMode);
+ }
+
+ $params = $this->getParams($params);
+
+ if ($this->_resultCache && $this->_type == self::SELECT) {
+ $cacheDriver = $this->getResultCacheDriver();
+
+ $dql = $this->getDql();
+ // calculate hash for dql query
+ $hash = md5($dql . var_export($params, true));
+
+ $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash);
+
+ if ($cached === false) {
+ // cache miss
+ $stmt = $this->_execute($params);
+ $this->_hydrator->setQueryComponents($this->_queryComponents);
+ $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
+
+ $cached = $this->getCachedForm($result);
+ $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan());
+ } else {
+ $result = $this->_constructQueryFromCache($cached);
+ }
+ } else {
+ $stmt = $this->_execute($params);
+
+ if (is_integer($stmt)) {
+ $result = $stmt;
+ } else {
+ $this->_hydrator->setQueryComponents($this->_queryComponents);
+ $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
+ }
+ }
+
+ return $result;
+ }
+
+ protected function _getDqlCallback()
+ {
+ $callback = false;
+ if ( ! empty($this->_dqlParts['from'])) {
+ switch ($this->_type) {
+ case self::DELETE:
+ $callback = array(
+ 'callback' => 'preDqlDelete',
+ 'const' => IPF_ORM_Event::RECORD_DQL_DELETE
+ );
+ break;
+ case self::UPDATE:
+ $callback = array(
+ 'callback' => 'preDqlUpdate',
+ 'const' => IPF_ORM_Event::RECORD_DQL_UPDATE
+ );
+ break;
+ case self::SELECT:
+ $callback = array(
+ 'callback' => 'preDqlSelect',
+ 'const' => IPF_ORM_Event::RECORD_DQL_SELECT
+ );
+ break;
+ }
+ }
+
+ return $callback;
+ }
+
+ protected function _preQuery()
+ {
+ if ( ! $this->_preQueried && IPF_ORM_Manager::getInstance()->getAttribute('use_dql_callbacks')) {
+ $this->_preQueried = true;
+
+ $callback = $this->_getDqlCallback();
+
+ // if there is no callback for the query type, then we can return early
+ if ( ! $callback) {
+ return;
+ }
+
+ $copy = $this->copy();
+ $copy->getSqlQuery();
+
+ foreach ($copy->getQueryComponents() as $alias => $component) {
+ $table = $component['table'];
+ $record = $table->getRecordInstance();
+
+ // Trigger preDql*() callback event
+ $params = array('component' => $component, 'alias' => $alias);
+ $event = new IPF_ORM_Event($record, $callback['const'], $this, $params);
+
+ $record->$callback['callback']($event);
+ $table->getRecordListener()->$callback['callback']($event);
+ }
+ }
+
+ // Invoke preQuery() hook on IPF_ORM_Query for child classes which implement this hook
+ $this->preQuery();
+ }
+
+ public function preQuery()
+ {
+ }
+
+ protected function _constructQueryFromCache($cached)
+ {
+ $cached = unserialize($cached);
+ $this->_tableAliasMap = $cached[2];
+ $customComponent = $cached[0];
+
+ $queryComponents = array();
+ $cachedComponents = $cached[1];
+ foreach ($cachedComponents as $alias => $components) {
+ $e = explode('.', $components[0]);
+ if (count($e) === 1) {
+ $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]);
+ } else {
+ $queryComponents[$alias]['parent'] = $e[0];
+ $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]);
+ $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable();
+ }
+ if (isset($components[1])) {
+ $queryComponents[$alias]['agg'] = $components[1];
+ }
+ if (isset($components[2])) {
+ $queryComponents[$alias]['map'] = $components[2];
+ }
+ }
+ $this->_queryComponents = $queryComponents;
+
+ return $customComponent;
+ }
+
+ public function getCachedForm($customComponent = null)
+ {
+ $componentInfo = array();
+
+ foreach ($this->getQueryComponents() as $alias => $components) {
+ if ( ! isset($components['parent'])) {
+ $componentInfo[$alias][] = $components['table']->getComponentName();
+ } else {
+ $componentInfo[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias();
+ }
+ if (isset($components['agg'])) {
+ $componentInfo[$alias][] = $components['agg'];
+ }
+ if (isset($components['map'])) {
+ $componentInfo[$alias][] = $components['map'];
+ }
+ }
+
+ return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap()));
+ }
+
+ public function addSelect($select)
+ {
+ return $this->_addDqlQueryPart('select', $select, true);
+ }
+
+ public function addTableAlias($tableAlias, $componentAlias)
+ {
+ return $this->addSqlTableAlias($tableAlias, $componentAlias);
+ }
+
+ public function addSqlTableAlias($sqlTableAlias, $componentAlias)
+ {
+ $this->_tableAliasMap[$sqlTableAlias] = $componentAlias;
+ return $this;
+ }
+
+ public function addFrom($from)
+ {
+ return $this->_addDqlQueryPart('from', $from, true);
+ }
+
+ public function addWhere($where, $params = array())
+ {
+ if (is_array($params)) {
+ $this->_params['where'] = array_merge($this->_params['where'], $params);
+ } else {
+ $this->_params['where'][] = $params;
+ }
+ return $this->_addDqlQueryPart('where', $where, true);
+ }
+
+ public function whereIn($expr, $params = array(), $not = false)
+ {
+ $params = (array) $params;
+
+ // if there's no params, return (else we'll get a WHERE IN (), invalid SQL)
+ if (!count($params))
+ return $this;
+
+ $a = array();
+ foreach ($params as $k => $value) {
+ if ($value instanceof IPF_ORM_Expression) {
+ $value = $value->getSql();
+ unset($params[$k]);
+ } else {
+ $value = '?';
+ }
+ $a[] = $value;
+ }
+
+ $this->_params['where'] = array_merge($this->_params['where'], $params);
+
+ $where = $expr . ($not === true ? ' NOT ':'') . ' IN (' . implode(', ', $a) . ')';
+
+ return $this->_addDqlQueryPart('where', $where, true);
+ }
+
+ public function whereNotIn($expr, $params = array())
+ {
+ return $this->whereIn($expr, $params, true);
+ }
+
+ public function addGroupBy($groupby)
+ {
+ return $this->_addDqlQueryPart('groupby', $groupby, true);
+ }
+
+ public function addHaving($having, $params = array())
+ {
+ if (is_array($params)) {
+ $this->_params['having'] = array_merge($this->_params['having'], $params);
+ } else {
+ $this->_params['having'][] = $params;
+ }
+ return $this->_addDqlQueryPart('having', $having, true);
+ }
+
+ public function addOrderBy($orderby)
+ {
+ return $this->_addDqlQueryPart('orderby', $orderby, true);
+ }
+
+ public function select($select)
+ {
+ return $this->_addDqlQueryPart('select', $select);
+ }
+
+ public function distinct($flag = true)
+ {
+ $this->_sqlParts['distinct'] = (bool) $flag;
+ return $this;
+ }
+
+ public function forUpdate($flag = true)
+ {
+ $this->_sqlParts[self::FOR_UPDATE] = (bool) $flag;
+ return $this;
+ }
+
+ public function delete()
+ {
+ $this->_type = self::DELETE;
+ return $this;
+ }
+
+ public function update($update)
+ {
+ $this->_type = self::UPDATE;
+ return $this->_addDqlQueryPart('from', $update);
+ }
+
+ public function set($key, $value, $params = null)
+ {
+ if (is_array($key)) {
+ foreach ($key as $k => $v) {
+ $this->set($k, '?', array($v));
+ }
+ return $this;
+ } else {
+ if ($params !== null) {
+ if (is_array($params)) {
+ $this->_params['set'] = array_merge($this->_params['set'], $params);
+ } else {
+ $this->_params['set'][] = $params;
+ }
+ }
+
+ $this->_pendingSetParams[$key] = $value;
+
+ return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true);
+ }
+ }
+
+ public function from($from)
+ {
+ return $this->_addDqlQueryPart('from', $from);
+ }
+
+ public function innerJoin($join, $params = array())
+ {
+ if (is_array($params)) {
+ $this->_params['join'] = array_merge($this->_params['join'], $params);
+ } else {
+ $this->_params['join'][] = $params;
+ }
+
+ return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true);
+ }
+
+ public function leftJoin($join, $params = array())
+ {
+ if (is_array($params)) {
+ $this->_params['join'] = array_merge($this->_params['join'], $params);
+ } else {
+ $this->_params['join'][] = $params;
+ }
+
+ return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true);
+ }
+
+ public function groupBy($groupby)
+ {
+ return $this->_addDqlQueryPart('groupby', $groupby);
+ }
+
+ public function where($where, $params = array())
+ {
+ $this->_params['where'] = array();
+ if (is_array($params)) {
+ $this->_params['where'] = $params;
+ } else {
+ $this->_params['where'][] = $params;
+ }
+
+ return $this->_addDqlQueryPart('where', $where);
+ }
+
+ public function having($having, $params = array())
+ {
+ $this->_params['having'] = array();
+ if (is_array($params)) {
+ $this->_params['having'] = $params;
+ } else {
+ $this->_params['having'][] = $params;
+ }
+
+ return $this->_addDqlQueryPart('having', $having);
+ }
+
+ public function orderBy($orderby)
+ {
+ return $this->_addDqlQueryPart('orderby', $orderby);
+ }
+
+ public function limit($limit)
+ {
+ return $this->_addDqlQueryPart('limit', $limit);
+ }
+
+ public function offset($offset)
+ {
+ return $this->_addDqlQueryPart('offset', $offset);
+ }
+
+ public function getSql($params = array())
+ {
+ return $this->getSqlQuery($params);
+ }
+
+ protected function clear()
+ {
+ $this->_sqlParts = array(
+ 'select' => array(),
+ 'distinct' => false,
+ 'forUpdate' => false,
+ 'from' => array(),
+ 'set' => array(),
+ 'join' => array(),
+ 'where' => array(),
+ 'groupby' => array(),
+ 'having' => array(),
+ 'orderby' => array(),
+ 'limit' => false,
+ 'offset' => false,
+ );
+ }
+
+ public function setHydrationMode($hydrationMode)
+ {
+ $this->_hydrator->setHydrationMode($hydrationMode);
+ return $this;
+ }
+
+ public function getAliasMap()
+ {
+ return $this->_queryComponents;
+ }
+
+ public function getQueryComponents()
+ {
+ return $this->_queryComponents;
+ }
+
+ public function getParts()
+ {
+ return $this->getSqlParts();
+ }
+
+ public function getSqlParts()
+ {
+ return $this->_sqlParts;
+ }
+
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ public function useCache($driver = true, $timeToLive = null)
+ {
+ return $this->useResultCache($driver, $timeToLive);
+ }
+
+ public function useResultCache($driver = true, $timeToLive = null)
+ {
+ if ($driver !== null && $driver !== true && ! ($driver instanceOf IPF_ORM_Cache_Interface)) {
+ $msg = 'First argument should be instance of IPF_ORM_Cache_Interface or null.';
+ throw new IPF_ORM_Exception($msg);
+ }
+ $this->_resultCache = $driver;
+
+ return $this->setResultCacheLifeSpan($timeToLive);
+ }
+
+ public function useQueryCache(IPF_ORM_Cache_Interface $driver, $timeToLive = null)
+ {
+ $this->_queryCache = $driver;
+ return $this->setQueryCacheLifeSpan($timeToLive);
+ }
+
+ public function expireCache($expire = true)
+ {
+ return $this->expireResultCache($expire);
+ }
+
+ public function expireResultCache($expire = true)
+ {
+ $this->_expireResultCache = true;
+ return $this;
+ }
+
+ public function expireQueryCache($expire = true)
+ {
+ $this->_expireQueryCache = true;
+ return $this;
+ }
+
+ public function setCacheLifeSpan($timeToLive)
+ {
+ return $this->setResultCacheLifeSpan($timeToLive);
+ }
+
+ public function setResultCacheLifeSpan($timeToLive)
+ {
+ if ($timeToLive !== null) {
+ $timeToLive = (int) $timeToLive;
+ }
+ $this->_resultCacheTTL = $timeToLive;
+
+ return $this;
+ }
+
+ public function getResultCacheLifeSpan()
+ {
+ return $this->_resultCacheTTL;
+ }
+
+ public function setQueryCacheLifeSpan($timeToLive)
+ {
+ if ($timeToLive !== null) {
+ $timeToLive = (int) $timeToLive;
+ }
+ $this->_queryCacheTTL = $timeToLive;
+
+ return $this;
+ }
+
+ public function getQueryCacheLifeSpan()
+ {
+ return $this->_queryCacheTTL;
+ }
+
+ public function getCacheDriver()
+ {
+ return $this->getResultCacheDriver();
+ }
+
+ public function getResultCacheDriver()
+ {
+ if ($this->_resultCache instanceof IPF_ORM_Cache_Interface) {
+ return $this->_resultCache;
+ } else {
+ return $this->_conn->getResultCacheDriver();
+ }
+ }
+
+ public function getQueryCacheDriver()
+ {
+ if ($this->_queryCache instanceof IPF_ORM_Cache_Interface) {
+ return $this->_queryCache;
+ } else {
+ return $this->_conn->getQueryCacheDriver();
+ }
+ }
+
+ public function getConnection()
+ {
+ return $this->_conn;
+ }
+
+ protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
+ {
+ if ($append) {
+ $this->_dqlParts[$queryPartName][] = $queryPart;
+ } else {
+ $this->_dqlParts[$queryPartName] = array($queryPart);
+ }
+
+ $this->_state = IPF_ORM_Query::STATE_DIRTY;
+ return $this;
+ }
+
+ protected function _processDqlQueryPart($queryPartName, $queryParts)
+ {
+ $this->removeSqlQueryPart($queryPartName);
+
+ if (is_array($queryParts) && ! empty($queryParts)) {
+ foreach ($queryParts as $queryPart) {
+ $parser = $this->_getParser($queryPartName);
+ $sql = $parser->parse($queryPart);
+ if (isset($sql)) {
+ if ($queryPartName == 'limit' || $queryPartName == 'offset') {
+ $this->setSqlQueryPart($queryPartName, $sql);
+ } else {
+ $this->addSqlQueryPart($queryPartName, $sql);
+ }
+ }
+ }
+ }
+ }
+
+ protected function _getParser($name)
+ {
+ if ( ! isset($this->_parsers[$name])) {
+ $class = 'IPF_ORM_Query_' . ucwords(strtolower($name));
+
+ //IPF_ORM::autoload($class);
+
+ if ( ! class_exists($class)) {
+ throw new IPF_ORM_Exception('Unknown parser ' . $name);
+ }
+
+ $this->_parsers[$name] = new $class($this, $this->_tokenizer);
+ }
+
+ return $this->_parsers[$name];
+ }
+
+ abstract public function getSqlQuery($params = array());
+
+ abstract public function parseDqlQuery($query);
+
+ public function parseQuery($query)
+ {
+ return $this->parseDqlQuery($query);
+ }
+
+ public function getQuery($params = array())
+ {
+ return $this->getSqlQuery($params);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Check
+{
+ protected $table;
+
+ protected $sql;
+
+ protected $_tokenizer;
+
+ public function __construct($table)
+ {
+ if ( ! ($table instanceof IPF_ORM_Table)) {
+ $table = IPF_ORM_Manager::getInstance()
+ ->getCurrentConnection()
+ ->getTable($table);
+ }
+ $this->table = $table;
+ $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
+ }
+
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+ public function parse($dql)
+ {
+ $this->sql = $this->parseClause($dql);
+ }
+
+ public function parseClause($dql)
+ {
+ $parts = $this->_tokenizer->sqlExplode($dql, ' AND ');
+
+ if (count($parts) > 1) {
+ $ret = array();
+ foreach ($parts as $part) {
+ $ret[] = $this->parseSingle($part);
+ }
+
+ $r = implode(' AND ', $ret);
+ } else {
+ $parts = $this->_tokenizer->quoteExplode($dql, ' OR ');
+ if (count($parts) > 1) {
+ $ret = array();
+ foreach ($parts as $part) {
+ $ret[] = $this->parseClause($part);
+ }
+
+ $r = implode(' OR ', $ret);
+ } else {
+ $ret = $this->parseSingle($dql);
+ return $ret;
+ }
+ }
+ return '(' . $r . ')';
+ }
+
+ public function parseSingle($part)
+ {
+ $e = explode(' ', $part);
+
+ $e[0] = $this->parseFunction($e[0]);
+
+ switch ($e[1]) {
+ case '>':
+ case '<':
+ case '=':
+ case '!=':
+ case '<>':
+
+ break;
+ default:
+ throw new IPF_ORM_Exception('Unknown operator ' . $e[1]);
+ }
+
+ return implode(' ', $e);
+ }
+ public function parseFunction($dql)
+ {
+ if (($pos = strpos($dql, '(')) !== false) {
+ $func = substr($dql, 0, $pos);
+ $value = substr($dql, ($pos + 1), -1);
+
+ $expr = $this->table->getConnection()->expression;
+
+ if ( ! method_exists($expr, $func)) {
+ throw new IPF_ORM_Exception('Unknown function ' . $func);
+ }
+
+ $func = $expr->$func($value);
+ }
+ return $func;
+ }
+
+ public function getSql()
+ {
+ return $this->sql;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Query_Condition extends IPF_ORM_Query_Part
+{
+ public function parse($str)
+ {
+ $tmp = trim($str);
+
+ $parts = $this->_tokenizer->bracketExplode($str, array(' \&\& ', ' AND '), '(', ')');
+
+ if (count($parts) > 1) {
+ $ret = array();
+ foreach ($parts as $part) {
+ $part = $this->_tokenizer->bracketTrim($part, '(', ')');
+ $ret[] = $this->parse($part);
+ }
+ $r = implode(' AND ', $ret);
+ } else {
+
+ $parts = $this->_tokenizer->bracketExplode($str, array(' \|\| ', ' OR '), '(', ')');
+ if (count($parts) > 1) {
+ $ret = array();
+ foreach ($parts as $part) {
+ $part = $this->_tokenizer->bracketTrim($part, '(', ')');
+ $ret[] = $this->parse($part);
+ }
+ $r = implode(' OR ', $ret);
+ } else {
+ // Fix for #710
+ if (substr($parts[0],0,1) == '(' && substr($parts[0], -1) == ')') {
+ return $this->parse(substr($parts[0], 1, -1));
+ } else {
+ // Processing NOT here
+ if (strtoupper(substr($parts[0], 0, 4)) === 'NOT ') {
+ $r = 'NOT ('.$this->parse(substr($parts[0], 4)).')';
+ } else {
+ return $this->load($parts[0]);
+ }
+ }
+ }
+ }
+
+ return '(' . $r . ')';
+ }
+
+ public function parseLiteralValue($value)
+ {
+ // check that value isn't a string
+ if (strpos($value, '\'') === false) {
+ // parse booleans
+ $value = $this->query->getConnection()
+ ->dataDict->parseBoolean($value);
+
+ $a = explode('.', $value);
+
+ if (count($a) > 1) {
+ // either a float or a component..
+
+ if ( ! is_numeric($a[0])) {
+ // a component found
+ $field = array_pop($a);
+ $reference = implode('.', $a);
+ $value = $this->query->getTableAlias($reference). '.' . $field;
+ }
+ }
+ } else {
+ // string literal found
+ }
+ return $value;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Filter implements IPF_ORM_Query_Filter_Interface
+{
+ public function preQuery(IPF_ORM_Query $query)
+ {
+ }
+
+ public function postQuery(IPF_ORM_Query $query)
+ {
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Filter_Chain
+{
+ protected $_filters = array();
+
+ public function add(IPF_ORM_Query_Filter $filter)
+ {
+ $this->_filters[] = $filter;
+ }
+
+ public function get($key)
+ {
+ if ( ! isset($this->_filters[$key])) {
+ throw new IPF_ORM_Exception('Unknown filter ' . $key);
+ }
+ return $this->_filters[$key];
+ }
+
+ public function set($key, IPF_ORM_Query_Filter $listener)
+ {
+ $this->_filters[$key] = $listener;
+ }
+
+ public function preQuery(IPF_ORM_Query $query)
+ {
+ foreach ($this->_filters as $filter) {
+ $filter->preQuery($query);
+ }
+ }
+
+ public function postQuery(IPF_ORM_Query $query)
+ {
+ foreach ($this->_filters as $filter) {
+ $filter->postQuery($query);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+interface IPF_ORM_Query_Filter_Interface
+{
+ public function preQuery(IPF_ORM_Query $query);
+ public function postQuery(IPF_ORM_Query $query);
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_From extends IPF_ORM_Query_Part
+{
+ public function parse($str, $return = false)
+ {
+ $str = trim($str);
+ $parts = $this->_tokenizer->bracketExplode($str, 'JOIN');
+
+ $from = $return ? array() : null;
+
+ $operator = false;
+
+ switch (trim($parts[0])) {
+ case 'INNER':
+ $operator = ':';
+ case 'LEFT':
+ array_shift($parts);
+ break;
+ }
+
+ $last = '';
+
+ foreach ($parts as $k => $part) {
+ $part = trim($part);
+
+ if (empty($part)) {
+ continue;
+ }
+
+ $e = explode(' ', $part);
+
+ if (end($e) == 'INNER' || end($e) == 'LEFT') {
+ $last = array_pop($e);
+ }
+ $part = implode(' ', $e);
+
+ foreach ($this->_tokenizer->bracketExplode($part, ',') as $reference) {
+ $reference = trim($reference);
+ $e = explode(' ', $reference);
+ $e2 = explode('.', $e[0]);
+
+ if ($operator) {
+ $e[0] = array_shift($e2) . $operator . implode('.', $e2);
+ }
+
+ if ($return) {
+ $from[] = $e;
+ } else {
+ $table = $this->query->load(implode(' ', $e));
+ }
+ }
+
+ $operator = ($last == 'INNER') ? ':' : '.';
+ }
+ return $from;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Groupby extends IPF_ORM_Query_Part
+{
+ public function parse($str, $append = false)
+ {
+ $r = array();
+ foreach (explode(',', $str) as $reference) {
+ $reference = trim($reference);
+
+ $r[] = $this->query->parseClause($reference);
+ }
+ return implode(', ', $r);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Having extends IPF_ORM_Query_Condition
+{
+ private function parseAggregateFunction($func)
+ {
+ $pos = strpos($func, '(');
+
+ if ($pos !== false) {
+ $funcs = array();
+
+ $name = substr($func, 0, $pos);
+ $func = substr($func, ($pos + 1), -1);
+ $params = $this->_tokenizer->bracketExplode($func, ',', '(', ')');
+
+ foreach ($params as $k => $param) {
+ $params[$k] = $this->parseAggregateFunction($param);
+ }
+
+ $funcs = $name . '(' . implode(', ', $params) . ')';
+
+ return $funcs;
+
+ } else {
+ if ( ! is_numeric($func)) {
+ $a = explode('.', $func);
+
+ if (count($a) > 1) {
+ $field = array_pop($a);
+ $reference = implode('.', $a);
+ $map = $this->query->load($reference, false);
+ $field = $map['table']->getColumnName($field);
+ $func = $this->query->getTableAlias($reference) . '.' . $field;
+ } else {
+ $field = end($a);
+ $func = $this->query->getAggregateAlias($field);
+ }
+ return $func;
+ } else {
+ return $func;
+ }
+ }
+ }
+
+ final public function load($having)
+ {
+ $tokens = $this->_tokenizer->bracketExplode($having, ' ', '(', ')');
+ $part = $this->parseAggregateFunction(array_shift($tokens));
+ $operator = array_shift($tokens);
+ $value = implode(' ', $tokens);
+ $part .= ' ' . $operator . ' ' . $value;
+ // check the RHS for aggregate functions
+ if (strpos($value, '(') !== false) {
+ $value = $this->parseAggregateFunction($value);
+ }
+ return $part;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_JoinCondition extends IPF_ORM_Query_Condition
+{
+ public function load($condition)
+ {
+ $condition = trim($condition);
+
+ $e = $this->_tokenizer->sqlExplode($condition);
+
+ if (count($e) > 2) {
+ $expr = new IPF_ORM_Expression($e[0], $this->query->getConnection());
+ $e[0] = $expr->getSql();
+
+ $operator = $e[1];
+
+ if (substr(trim($e[2]), 0, 1) != '(') {
+ $expr = new IPF_ORM_Expression($e[2], $this->query->getConnection());
+ $e[2] = $expr->getSql();
+ }
+
+ // We need to check for agg functions here
+ $hasLeftAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[0], $leftMatches);
+
+ if ($hasLeftAggExpression) {
+ $e[0] = $leftMatches[2];
+ }
+
+ $hasRightAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[2], $rightMatches);
+
+ if ($hasRightAggExpression) {
+ $e[2] = $rightMatches[2];
+ }
+
+ $a = explode('.', $e[0]);
+ $field = array_pop($a);
+ $reference = implode('.', $a);
+ $value = $e[2];
+
+ $conn = $this->query->getConnection();
+ $alias = $this->query->getTableAlias($reference);
+ $map = $this->query->getAliasDeclaration($reference);
+ $table = $map['table'];
+ // check if value is enumerated value
+ $enumIndex = $table->enumIndex($field, trim($value, "'"));
+
+ if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ $enumIndex = $conn->quote($enumIndex, 'text');
+ }
+
+ // FIX: Issues with "(" XXX ")"
+ if ($hasRightAggExpression) {
+ $value = '(' . $value . ')';
+ }
+
+ if (substr($value, 0, 1) == '(') {
+ // trim brackets
+ $trimmed = $this->_tokenizer->bracketTrim($value);
+
+ if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
+ // subquery found
+ $q = $this->query->createSubquery();
+
+ // Change due to bug "(" XXX ")"
+ //$value = '(' . $q->parseQuery($trimmed)->getQuery() . ')';
+ $value = $q->parseQuery($trimmed)->getQuery();
+ } elseif (substr($trimmed, 0, 4) == 'SQL:') {
+ // Change due to bug "(" XXX ")"
+ //$value = '(' . substr($trimmed, 4) . ')';
+ $value = substr($trimmed, 4);
+ } else {
+ // simple in expression found
+ $e = $this->_tokenizer->sqlExplode($trimmed, ',');
+
+ $value = array();
+ foreach ($e as $part) {
+ $index = $table->enumIndex($field, trim($part, "'"));
+
+ if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ $index = $conn->quote($index, 'text');
+ }
+
+ if ($index !== false) {
+ $value[] = $index;
+ } else {
+ $value[] = $this->parseLiteralValue($part);
+ }
+ }
+
+ // Change due to bug "(" XXX ")"
+ //$value = '(' . implode(', ', $value) . ')';
+ $value = implode(', ', $value);
+ }
+ } else {
+ if ($enumIndex !== false) {
+ $value = $enumIndex;
+ } else {
+ $value = $this->parseLiteralValue($value);
+ }
+ }
+
+ switch ($operator) {
+ case '<':
+ case '>':
+ case '=':
+ case '!=':
+ if ($enumIndex !== false) {
+ $value = $enumIndex;
+ }
+ default:
+ $leftExpr = (($hasLeftAggExpression) ? $leftMatches[1] . '(' : '')
+ . $alias . '.' . $field
+ . (($hasLeftAggExpression) ? $leftMatches[3] . ')' : '') ;
+
+ $rightExpr = (($hasRightAggExpression) ? $rightMatches[1] . '(' : '')
+ . $value
+ . (($hasRightAggExpression) ? $rightMatches[3] . ')' : '') ;
+
+ $condition = $leftExpr . ' ' . $operator . ' ' . $rightExpr;
+ }
+
+ }
+
+ return $condition;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Limit extends IPF_ORM_Query_Part
+{
+ public function parse($limit)
+ {
+ return (int) $limit;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Offset extends IPF_ORM_Query_Part
+{
+ public function parse($offset)
+ {
+ return (int) $offset;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Orderby extends IPF_ORM_Query_Part
+{
+ public function parse($str, $append = false)
+ {
+ $ret = array();
+
+ foreach (explode(',', trim($str)) as $r) {
+ $r = $this->query->parseClause($r);
+
+ $ret[] = $r;
+ }
+ return $ret;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Parser
+{
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Query_Part
+{
+ protected $query;
+
+ protected $_tokenizer;
+
+ public function __construct($query, IPF_ORM_Query_Tokenizer $tokenizer = null)
+ {
+ $this->query = $query;
+ if ( ! $tokenizer) {
+ $tokenizer = new IPF_ORM_Query_Tokenizer();
+ }
+ $this->_tokenizer = $tokenizer;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Registry
+{
+ protected $_queries = array();
+
+ public function add($key, $query)
+ {
+ if (strpos($key, '/') === false) {
+ $this->_queries[$key] = $query;
+ } else {
+ // namespace found
+ $e = explode('/', $key);
+
+ $this->_queries[$e[0]][$e[1]] = $query;
+ }
+ }
+
+ public function get($key, $namespace = null)
+ {
+ if (isset($namespace)) {
+ if ( ! isset($this->_queries[$namespace][$key])) {
+ throw new IPF_ORM_Exception('A query with the name ' . $namespace . '/' . $key . ' does not exist.');
+ }
+ $query = $this->_queries[$namespace][$key];
+ } else {
+ if ( ! isset($this->_queries[$key])) {
+ throw new IPF_ORM_Exception('A query with the name ' . $key . ' does not exist.');
+ }
+ $query = $this->_queries[$key];
+ }
+
+ if ( ! ($query instanceof IPF_ORM_Query)) {
+ $query = IPF_ORM_Query::create()->parseQuery($query);
+ }
+
+ return $query;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Select extends IPF_ORM_Query_Part
+{
+ public function parse($dql)
+ {
+ $this->query->parseSelect($dql);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Set extends IPF_ORM_Query_Part
+{
+ public function parse($dql)
+ {
+ $terms = $this->_tokenizer->sqlExplode($dql, ' ');
+ foreach ($terms as $term) {
+ preg_match_all("/[a-z0-9_]+\.[a-z0-9_]+[\.[a-z0-9]+]*/i", $term, $m);
+
+ if (isset($m[0])) {
+ foreach ($m[0] as $part) {
+ $e = explode('.', trim($part));
+ $field = array_pop($e);
+
+ $reference = implode('.', $e);
+
+ $alias = $this->query->getTableAlias($reference);
+ $map = $this->query->getAliasDeclaration($reference);
+
+ $dql = str_replace($part, $map['table']->getColumnName($field), $dql);
+ }
+ }
+ }
+ return $dql;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Tokenizer
+{
+ public function tokenizeQuery($query)
+ {
+ $parts = array();
+ $tokens = $this->sqlExplode($query, ' ');
+
+ foreach ($tokens as $index => $token) {
+ $token = trim($token);
+ switch (strtolower($token)) {
+ case 'delete':
+ case 'update':
+ case 'select':
+ case 'set':
+ case 'from':
+ case 'where':
+ case 'limit':
+ case 'offset':
+ case 'having':
+ $p = $token;
+ //$parts[$token] = array();
+ $parts[$token] = '';
+ break;
+ case 'order':
+ case 'group':
+ $i = ($index + 1);
+ if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
+ $p = $token;
+ $parts[$token] = '';
+ //$parts[$token] = array();
+ } else {
+ $parts[$p] .= "$token ";
+ //$parts[$p][] = $token;
+ }
+ break;
+ case 'by':
+ continue;
+ default:
+ if ( ! isset($p)) {
+ throw new IPF_ORM_Exception(
+ "Couldn't tokenize query. Encountered invalid token: '$token'.");
+ }
+
+ $parts[$p] .= "$token ";
+ //$parts[$p][] = $token;
+ }
+ }
+ return $parts;
+ }
+
+ public function bracketTrim($str, $e1 = '(', $e2 = ')')
+ {
+ if (substr($str, 0, 1) === $e1 && substr($str, -1) === $e2) {
+ return substr($str, 1, -1);
+ } else {
+ return $str;
+ }
+ }
+
+ public function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
+ {
+ if (is_array($d)) {
+ $a = preg_split('#('.implode('|', $d).')#i', $str);
+ $d = stripslashes($d[0]);
+ } else {
+ $a = explode($d, $str);
+ }
+
+ $i = 0;
+ $term = array();
+ foreach($a as $key=>$val) {
+ if (empty($term[$i])) {
+ $term[$i] = trim($val);
+ $s1 = substr_count($term[$i], $e1);
+ $s2 = substr_count($term[$i], $e2);
+
+ if ($s1 == $s2) {
+ $i++;
+ }
+ } else {
+ $term[$i] .= $d . trim($val);
+ $c1 = substr_count($term[$i], $e1);
+ $c2 = substr_count($term[$i], $e2);
+
+ if ($c1 == $c2) {
+ $i++;
+ }
+ }
+ }
+ return $term;
+ }
+
+ public function quoteExplode($str, $d = ' ')
+ {
+ if (is_array($d)) {
+ $a = preg_split('/('.implode('|', $d).')/', $str);
+ $d = stripslashes($d[0]);
+ } else {
+ $a = explode($d, $str);
+ }
+
+ $i = 0;
+ $term = array();
+ foreach ($a as $key => $val) {
+ if (empty($term[$i])) {
+ $term[$i] = trim($val);
+
+ if ( ! (substr_count($term[$i], "'") & 1)) {
+ $i++;
+ }
+ } else {
+ $term[$i] .= $d . trim($val);
+
+ if ( ! (substr_count($term[$i], "'") & 1)) {
+ $i++;
+ }
+ }
+ }
+ return $term;
+ }
+
+ public function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
+ {
+ if ($d == ' ') {
+ $d = array(' ', '\s');
+ }
+ if (is_array($d)) {
+ $d = array_map('preg_quote', $d);
+
+ if (in_array(' ', $d)) {
+ $d[] = '\s';
+ }
+
+ $split = '#(' . implode('|', $d) . ')#';
+
+ $str = preg_split($split, $str);
+ $d = stripslashes($d[0]);
+ } else {
+ $str = explode($d, $str);
+ }
+
+ $i = 0;
+ $term = array();
+
+ foreach ($str as $key => $val) {
+ if (empty($term[$i])) {
+ $term[$i] = trim($val);
+
+ $s1 = substr_count($term[$i], $e1);
+ $s2 = substr_count($term[$i], $e2);
+
+ if (strpos($term[$i], '(') !== false) {
+ if ($s1 == $s2) {
+ $i++;
+ }
+ } else {
+ if ( ! (substr_count($term[$i], "'") & 1) &&
+ ! (substr_count($term[$i], "\"") & 1)) {
+ $i++;
+ }
+ }
+ } else {
+ $term[$i] .= $d . trim($val);
+ $c1 = substr_count($term[$i], $e1);
+ $c2 = substr_count($term[$i], $e2);
+
+ if (strpos($term[$i], '(') !== false) {
+ if ($c1 == $c2) {
+ $i++;
+ }
+ } else {
+ if ( ! (substr_count($term[$i], "'") & 1) &&
+ ! (substr_count($term[$i], "\"") & 1)) {
+ $i++;
+ }
+ }
+ }
+ }
+ return $term;
+ }
+
+ public function clauseExplode($str, array $d, $e1 = '(', $e2 = ')')
+ {
+ if (is_array($d)) {
+ $d = array_map('preg_quote', $d);
+
+ if (in_array(' ', $d)) {
+ $d[] = '\s';
+ }
+
+ $split = '#(' . implode('|', $d) . ')#';
+
+ $str = preg_split($split, $str, -1, PREG_SPLIT_DELIM_CAPTURE);
+ }
+
+ $i = 0;
+ $term = array();
+
+ foreach ($str as $key => $val) {
+ if ($key & 1) {
+ if (isset($term[($i - 1)]) && ! is_array($term[($i - 1)])) {
+ $term[($i - 1)] = array($term[($i - 1)], $val);
+ }
+ continue;
+ }
+ if (empty($term[$i])) {
+ $term[$i] = $val;
+ } else {
+ $term[$i] .= $str[($key - 1)] . $val;
+ }
+
+ $c1 = substr_count($term[$i], $e1);
+ $c2 = substr_count($term[$i], $e2);
+
+ if (strpos($term[$i], '(') !== false) {
+ if ($c1 == $c2) {
+ $i++;
+ }
+ } else {
+ if ( ! (substr_count($term[$i], "'") & 1) &&
+ ! (substr_count($term[$i], "\"") & 1)) {
+ $i++;
+ }
+ }
+ }
+
+ if (isset($term[$i - 1])) {
+ $term[$i - 1] = array($term[$i - 1], '');
+ }
+
+ return $term;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Query_Where extends IPF_ORM_Query_Condition
+{
+ public function load($where)
+ {
+ $where = $this->_tokenizer->bracketTrim(trim($where));
+ $conn = $this->query->getConnection();
+ $terms = $this->_tokenizer->sqlExplode($where);
+
+ if (count($terms) > 1) {
+ if (substr($where, 0, 6) == 'EXISTS') {
+ return $this->parseExists($where, true);
+ } elseif (substr($where, 0, 10) == 'NOT EXISTS') {
+ return $this->parseExists($where, false);
+ }
+ }
+
+ if (count($terms) < 3) {
+ $terms = $this->_tokenizer->sqlExplode($where, array('=', '<', '<>', '>', '!='));
+ }
+
+ if (count($terms) > 1) {
+ $first = array_shift($terms);
+ $value = array_pop($terms);
+ $operator = trim(substr($where, strlen($first), -strlen($value)));
+ $table = null;
+ $field = null;
+
+ if (strpos($first, "'") === false && strpos($first, '(') === false) {
+ // normal field reference found
+ $a = explode('.', $first);
+
+ $field = array_pop($a);
+ $reference = implode('.', $a);
+
+ if (empty($reference)) {
+ $map = $this->query->getRootDeclaration();
+
+ $alias = $this->query->getTableAlias($this->query->getRootAlias());
+ $table = $map['table'];
+ } else {
+ $map = $this->query->load($reference, false);
+
+ $alias = $this->query->getTableAlias($reference);
+ $table = $map['table'];
+ }
+ }
+ $first = $this->query->parseClause($first);
+
+ $sql = $first . ' ' . $operator . ' ' . $this->parseValue($value, $table, $field);
+
+ return $sql;
+ } else {
+ return $where;
+ }
+ }
+
+ public function parseValue($value, IPF_ORM_Table $table = null, $field = null)
+ {
+ $conn = $this->query->getConnection();
+
+ if (substr($value, 0, 1) == '(') {
+ // trim brackets
+ $trimmed = $this->_tokenizer->bracketTrim($value);
+
+ if (substr($trimmed, 0, 4) == 'FROM' ||
+ substr($trimmed, 0, 6) == 'SELECT') {
+
+ // subquery found
+ $q = new IPF_ORM_Query();
+ $value = '(' . $this->query->createSubquery()->parseQuery($trimmed, false)->getQuery() . ')';
+
+ } elseif (substr($trimmed, 0, 4) == 'SQL:') {
+ $value = '(' . substr($trimmed, 4) . ')';
+ } else {
+ // simple in expression found
+ $e = $this->_tokenizer->sqlExplode($trimmed, ',');
+
+ $value = array();
+
+ $index = false;
+
+ foreach ($e as $part) {
+ if (isset($table) && isset($field)) {
+ $index = $table->enumIndex($field, trim($part, "'"));
+
+ if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ $index = $conn->quote($index, 'text');
+ }
+ }
+
+ if ($index !== false) {
+ $value[] = $index;
+ } else {
+ $value[] = $this->parseLiteralValue($part);
+ }
+ }
+
+ $value = '(' . implode(', ', $value) . ')';
+ }
+ } else if (substr($value, 0, 1) == ':' || $value === '?') {
+ // placeholder found
+ if (isset($table) && isset($field) && $table->getTypeOf($field) == 'enum') {
+ $this->query->addEnumParam($value, $table, $field);
+ } else {
+ $this->query->addEnumParam($value, null, null);
+ }
+ } else {
+ $enumIndex = false;
+ if (isset($table) && isset($field)) {
+ // check if value is enumerated value
+ $enumIndex = $table->enumIndex($field, trim($value, "'"));
+
+ if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ $enumIndex = $conn->quote($enumIndex, 'text');
+ }
+ }
+
+ if ($enumIndex !== false) {
+ $value = $enumIndex;
+ } else {
+ $value = $this->parseLiteralValue($value);
+ }
+ }
+ return $value;
+ }
+
+ public function parseExists($where, $negation)
+ {
+ $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS';
+
+ $pos = strpos($where, '(');
+
+ if ($pos == false) {
+ throw new IPF_ORM_Exception('Unknown expression, expected a subquery with () -marks');
+ }
+
+ $sub = $this->_tokenizer->bracketTrim(substr($where, $pos));
+
+ return $operator . ' (' . $this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')';
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_RawSql extends IPF_ORM_Query_Abstract
+{
+ private $fields = array();
+
+ public function parseDqlQueryPart($queryPartName, $queryPart, $append = false)
+ {
+ if ($queryPartName == 'select') {
+ $this->_parseSelectFields($queryPart);
+ return $this;
+ }
+ if ( ! isset($this->parts[$queryPartName])) {
+ $this->_sqlParts[$queryPartName] = array();
+ }
+
+ if ( ! $append) {
+ $this->_sqlParts[$queryPartName] = array($queryPart);
+ } else {
+ $this->_sqlParts[$queryPartName][] = $queryPart;
+ }
+ return $this;
+ }
+
+ protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
+ {
+ return $this->parseQueryPart($queryPartName, $queryPart, $append);
+ }
+
+ private function _parseSelectFields($queryPart){
+ preg_match_all('/{([^}{]*)}/U', $queryPart, $m);
+ $this->fields = $m[1];
+ $this->_sqlParts['select'] = array();
+ }
+
+ public function parseDqlQuery($query)
+ {
+ $this->_parseSelectFields($query);
+ $this->clear();
+
+ $tokens = $this->_tokenizer->sqlExplode($query, ' ');
+
+ $parts = array();
+ foreach ($tokens as $key => $part) {
+ $partLowerCase = strtolower($part);
+ switch ($partLowerCase) {
+ case 'select':
+ case 'from':
+ case 'where':
+ case 'limit':
+ case 'offset':
+ case 'having':
+ $type = $partLowerCase;
+ if ( ! isset($parts[$partLowerCase])) {
+ $parts[$partLowerCase] = array();
+ }
+ break;
+ case 'order':
+ case 'group':
+ $i = $key + 1;
+ if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
+ $type = $partLowerCase . 'by';
+ $parts[$type] = array();
+ } else {
+ //not a keyword so we add it to the previous type
+ $parts[$type][] = $part;
+ }
+ break;
+ case 'by':
+ continue;
+ default:
+ //not a keyword so we add it to the previous type.
+ if ( ! isset($parts[$type][0])) {
+ $parts[$type][0] = $part;
+ } else {
+ // why does this add to index 0 and not append to the
+ // array. If it had done that one could have used
+ // parseQueryPart.
+ $parts[$type][0] .= ' '.$part;
+ }
+ }
+ }
+
+ $this->_sqlParts = $parts;
+ $this->_sqlParts['select'] = array();
+
+ return $this;
+ }
+
+ public function getSqlQuery($params = array())
+ {
+ $select = array();
+
+ foreach ($this->fields as $field) {
+ $e = explode('.', $field);
+ if ( ! isset($e[1])) {
+ throw new IPF_ORM_Exception('All selected fields in Sql query must be in format tableAlias.fieldName');
+ }
+ // try to auto-add component
+ if ( ! $this->hasSqlTableAlias($e[0])) {
+ try {
+ $this->addComponent($e[0], ucwords($e[0]));
+ } catch (IPF_ORM_Exception $exception) {
+ throw new IPF_ORM_Exception('The associated component for table alias ' . $e[0] . ' couldn\'t be found.');
+ }
+ }
+
+ $componentAlias = $this->getComponentAlias($e[0]);
+
+ if ($e[1] == '*') {
+ foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) {
+ $field = $e[0] . '.' . $name;
+
+ $select[$componentAlias][$field] = $field . ' AS ' . $e[0] . '__' . $name;
+ }
+ } else {
+ $field = $e[0] . '.' . $e[1];
+ $select[$componentAlias][$field] = $field . ' AS ' . $e[0] . '__' . $e[1];
+ }
+ }
+
+ // force-add all primary key fields
+
+ foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) {
+ $map = $this->_queryComponents[$componentAlias];
+
+ foreach ((array) $map['table']->getIdentifierColumnNames() as $key) {
+ $field = $tableAlias . '.' . $key;
+
+ if ( ! isset($this->_sqlParts['select'][$field])) {
+ $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key;
+ }
+ }
+ }
+
+ // first add the fields of the root component
+ reset($this->_queryComponents);
+ $componentAlias = key($this->_queryComponents);
+
+ $q = 'SELECT ' . implode(', ', $select[$componentAlias]);
+ unset($select[$componentAlias]);
+
+ foreach ($select as $component => $fields) {
+ if ( ! empty($fields)) {
+ $q .= ', ' . implode(', ', $fields);
+ }
+ }
+
+ $string = $this->getInheritanceCondition($this->getRootAlias());
+ if ( ! empty($string)) {
+ $this->_sqlParts['where'][] = $string;
+ }
+ $copy = $this->_sqlParts;
+ unset($copy['select']);
+
+ $q .= ( ! empty($this->_sqlParts['from']))? ' FROM ' . implode(' ', $this->_sqlParts['from']) : '';
+ $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : '';
+ $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
+ $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']) : '';
+ $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
+ $q .= ( ! empty($this->_sqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : '';
+ $q .= ( ! empty($this->_sqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : '';
+
+ if ( ! empty($string)) {
+ array_pop($this->_sqlParts['where']);
+ }
+ return $q;
+ }
+
+ public function getFields()
+ {
+ return $this->fields;
+ }
+
+ public function addComponent($tableAlias, $path)
+ {
+ $tmp = explode(' ', $path);
+ $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
+
+ $e = explode('.', $tmp[0]);
+
+ $fullPath = $tmp[0];
+ $fullLength = strlen($fullPath);
+
+ $table = null;
+
+ $currPath = '';
+
+ if (isset($this->_queryComponents[$e[0]])) {
+ $table = $this->_queryComponents[$e[0]]['table'];
+
+ $currPath = $parent = array_shift($e);
+ }
+
+ foreach ($e as $k => $component) {
+ // get length of the previous path
+ $length = strlen($currPath);
+
+ // build the current component path
+ $currPath = ($currPath) ? $currPath . '.' . $component : $component;
+
+ $delimeter = substr($fullPath, $length, 1);
+
+ // if an alias is not given use the current path as an alias identifier
+ if (strlen($currPath) === $fullLength && isset($originalAlias)) {
+ $componentAlias = $originalAlias;
+ } else {
+ $componentAlias = $currPath;
+ }
+ if ( ! isset($table)) {
+ $conn = IPF_ORM_Manager::getInstance()
+ ->getConnectionForComponent($component);
+
+ $table = $conn->getTable($component);
+ $this->_queryComponents[$componentAlias] = array('table' => $table);
+ } else {
+ $relation = $table->getRelation($component);
+
+ $this->_queryComponents[$componentAlias] = array('table' => $relation->getTable(),
+ 'parent' => $parent,
+ 'relation' => $relation);
+ }
+ $this->addSqlTableAlias($tableAlias, $componentAlias);
+
+ $parent = $currPath;
+ }
+
+ return $this;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Record extends IPF_ORM_Record_Abstract implements Countable, IteratorAggregate, Serializable
+{
+ const STATE_DIRTY = 1;
+ const STATE_TDIRTY = 2;
+ const STATE_CLEAN = 3;
+ const STATE_PROXY = 4;
+ const STATE_TCLEAN = 5;
+ const STATE_LOCKED = 6;
+
+ protected $_node;
+ protected $_id = array();
+ protected $_data = array();
+ protected $_values = array();
+ protected $_state;
+ protected $_modified = array();
+ protected $_errorStack;
+ protected $_references = array();
+ protected $_pendingDeletes = array();
+ private static $_index = 1;
+ private $_oid;
+
+ public function __construct($table = null, $isNewEntry = false)
+ {
+ if (isset($table) && $table instanceof IPF_ORM_Table) {
+ $this->_table = $table;
+ $exists = ( ! $isNewEntry);
+ } else {
+ // get the table of this class
+ $class = get_class($this);
+ $this->_table = IPF_ORM::getTable($class);
+ $exists = false;
+ }
+
+ // Check if the current connection has the records table in its registry
+ // If not this record is only used for creating table definition and setting up
+ // relations.
+ if ( ! $this->_table->getConnection()->hasTable($this->_table->getComponentName())) {
+ return;
+ }
+
+ $this->_oid = self::$_index;
+
+ self::$_index++;
+
+ // get the data array
+ $this->_data = $this->_table->getData();
+
+ // get the column count
+ $count = count($this->_data);
+
+ $this->_values = $this->cleanData($this->_data);
+
+ $this->prepareIdentifiers($exists);
+
+ if ( ! $exists) {
+ if ($count > count($this->_values)) {
+ $this->_state = IPF_ORM_Record::STATE_TDIRTY;
+ } else {
+ $this->_state = IPF_ORM_Record::STATE_TCLEAN;
+ }
+
+ // set the default values for this record
+ $this->assignDefaultValues();
+ } else {
+ $this->_state = IPF_ORM_Record::STATE_CLEAN;
+
+ if ($count < $this->_table->getColumnCount()) {
+ $this->_state = IPF_ORM_Record::STATE_PROXY;
+ }
+ }
+
+ $repository = $this->_table->getRepository();
+ $repository->add($this);
+
+ $this->construct();
+ }
+
+ public static function _index()
+ {
+ return self::$_index;
+ }
+
+ public function setUp(){}
+ public function construct(){}
+
+ public function getOid()
+ {
+ return $this->_oid;
+ }
+
+ public function oid()
+ {
+ return $this->_oid;
+ }
+
+ public function isValid()
+ {
+ if ( ! $this->_table->getAttribute(IPF_ORM::ATTR_VALIDATE)) {
+ return true;
+ }
+ // Clear the stack from any previous errors.
+ $this->getErrorStack()->clear();
+
+ // Run validation process
+ $validator = new IPF_ORM_Validator();
+ $validator->validateRecord($this);
+ $this->validate();
+ if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) {
+ $this->validateOnInsert();
+ } else {
+ $this->validateOnUpdate();
+ }
+
+ return $this->getErrorStack()->count() == 0 ? true : false;
+ }
+
+ protected function validate(){}
+
+ protected function validateOnUpdate(){}
+
+ protected function validateOnInsert(){}
+
+ public function preSerialize($event){}
+
+ public function postSerialize($event){}
+
+ public function preUnserialize($event){}
+
+ public function postUnserialize($event){}
+
+ public function preSave($event){}
+
+ public function postSave($event){}
+
+ public function preDelete($event){}
+
+ public function postDelete($event){}
+
+ public function preUpdate($event){}
+
+ public function postUpdate($event){}
+
+ public function preInsert($event){}
+
+ public function postInsert($event){}
+
+ public function preDqlSelect($event){}
+
+ public function preDqlUpdate($event){}
+
+ public function preDqlDelete($event){}
+
+ public function getErrorStack(){
+ if ( ! $this->_errorStack) {
+ $this->_errorStack = new IPF_ORM_Validator_ErrorStack(get_class($this));
+ }
+
+ return $this->_errorStack;
+ }
+
+ public function errorStack($stack = null)
+ {
+ if ($stack !== null) {
+ if ( ! ($stack instanceof IPF_ORM_Validator_ErrorStack)) {
+ throw new IPF_ORM_Exception('Argument should be an instance of IPF_ORM_Validator_ErrorStack.');
+ }
+ $this->_errorStack = $stack;
+ } else {
+ return $this->getErrorStack();
+ }
+ }
+
+ public function assignDefaultValues($overwrite = false)
+ {
+ if ( ! $this->_table->hasDefaultValues()) {
+ return false;
+ }
+ foreach ($this->_data as $column => $value) {
+ $default = $this->_table->getDefaultValueOf($column);
+
+ if ($default === null) {
+ continue;
+ }
+
+ if ($value === self::$_null || $overwrite) {
+ $this->_data[$column] = $default;
+ $this->_modified[] = $column;
+ $this->_state = IPF_ORM_Record::STATE_TDIRTY;
+ }
+ }
+ }
+
+ public function cleanData(&$data)
+ {
+ $tmp = $data;
+ $data = array();
+
+ foreach ($this->getTable()->getFieldNames() as $fieldName) {
+ if (isset($tmp[$fieldName])) {
+ $data[$fieldName] = $tmp[$fieldName];
+ } else if (array_key_exists($fieldName, $tmp)) {
+ $data[$fieldName] = self::$_null;
+ } else if (!isset($this->_data[$fieldName])) {
+ $data[$fieldName] = self::$_null;
+ }
+ unset($tmp[$fieldName]);
+ }
+
+ return $tmp;
+ }
+
+ public function hydrate(array $data)
+ {
+ $this->_values = array_merge($this->_values, $this->cleanData($data));
+ $this->_data = array_merge($this->_data, $data);
+ $this->prepareIdentifiers(true);
+ }
+
+ private function prepareIdentifiers($exists = true)
+ {
+ switch ($this->_table->getIdentifierType()) {
+ case IPF_ORM::IDENTIFIER_AUTOINC:
+ case IPF_ORM::IDENTIFIER_SEQUENCE:
+ case IPF_ORM::IDENTIFIER_NATURAL:
+ $name = $this->_table->getIdentifier();
+ if (is_array($name)) {
+ $name = $name[0];
+ }
+ if ($exists) {
+ if (isset($this->_data[$name]) && $this->_data[$name] !== self::$_null) {
+ $this->_id[$name] = $this->_data[$name];
+ }
+ }
+ break;
+ case IPF_ORM::IDENTIFIER_COMPOSITE:
+ $names = $this->_table->getIdentifier();
+
+ foreach ($names as $name) {
+ if ($this->_data[$name] === self::$_null) {
+ $this->_id[$name] = null;
+ } else {
+ $this->_id[$name] = $this->_data[$name];
+ }
+ }
+ break;
+ }
+ }
+
+ public function serialize()
+ {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_SERIALIZE);
+
+ $this->preSerialize($event);
+
+ $vars = get_object_vars($this);
+
+ unset($vars['_references']);
+ unset($vars['_table']);
+ unset($vars['_errorStack']);
+ unset($vars['_filter']);
+ unset($vars['_node']);
+
+ $name = $this->_table->getIdentifier();
+ $this->_data = array_merge($this->_data, $this->_id);
+
+ foreach ($this->_data as $k => $v) {
+ if ($v instanceof IPF_ORM_Record && $this->_table->getTypeOf($k) != 'object') {
+ unset($vars['_data'][$k]);
+ } elseif ($v === self::$_null) {
+ unset($vars['_data'][$k]);
+ } else {
+ switch ($this->_table->getTypeOf($k)) {
+ case 'array':
+ case 'object':
+ $vars['_data'][$k] = serialize($vars['_data'][$k]);
+ break;
+ case 'gzip':
+ $vars['_data'][$k] = gzcompress($vars['_data'][$k]);
+ break;
+ case 'enum':
+ $vars['_data'][$k] = $this->_table->enumIndex($k, $vars['_data'][$k]);
+ break;
+ }
+ }
+ }
+
+ $str = serialize($vars);
+
+ $this->postSerialize($event);
+
+ return $str;
+ }
+
+ public function unserialize($serialized)
+ {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_UNSERIALIZE);
+
+ $this->preUnserialize($event);
+
+ $manager = IPF_ORM_Manager::getInstance();
+ $connection = $manager->getConnectionForComponent(get_class($this));
+
+ $this->_oid = self::$_index;
+ self::$_index++;
+
+ $this->_table = $connection->getTable(get_class($this));
+
+ $array = unserialize($serialized);
+
+ foreach($array as $k => $v) {
+ $this->$k = $v;
+ }
+
+ foreach ($this->_data as $k => $v) {
+ switch ($this->_table->getTypeOf($k)) {
+ case 'array':
+ case 'object':
+ $this->_data[$k] = unserialize($this->_data[$k]);
+ break;
+ case 'gzip':
+ $this->_data[$k] = gzuncompress($this->_data[$k]);
+ break;
+ case 'enum':
+ $this->_data[$k] = $this->_table->enumValue($k, $this->_data[$k]);
+ break;
+
+ }
+ }
+
+ $this->_table->getRepository()->add($this);
+
+ $this->cleanData($this->_data);
+
+ $this->prepareIdentifiers($this->exists());
+
+ $this->postUnserialize($event);
+ }
+
+ public function state($state = null)
+ {
+ if ($state == null) {
+ return $this->_state;
+ }
+
+ $err = false;
+ if (is_integer($state)) {
+ if ($state >= 1 && $state <= 6) {
+ $this->_state = $state;
+ } else {
+ $err = true;
+ }
+ } else if (is_string($state)) {
+ $upper = strtoupper($state);
+
+ $const = 'IPF_ORM_Record::STATE_' . $upper;
+ if (defined($const)) {
+ $this->_state = constant($const);
+ } else {
+ $err = true;
+ }
+ }
+
+ if ($this->_state === IPF_ORM_Record::STATE_TCLEAN ||
+ $this->_state === IPF_ORM_Record::STATE_CLEAN) {
+ $this->_modified = array();
+ }
+
+ if ($err) {
+ throw new IPF_ORM_Exception('Unknown record state ' . $state);
+ }
+ }
+
+ public function refresh($deep = false)
+ {
+ $id = $this->identifier();
+ if ( ! is_array($id)) {
+ $id = array($id);
+ }
+ if (empty($id)) {
+ return false;
+ }
+ $id = array_values($id);
+
+ if ($deep) {
+ $query = $this->getTable()->createQuery();
+ foreach (array_keys($this->_references) as $name) {
+ $query->leftJoin(get_class($this) . '.' . $name);
+ }
+ $query->where(implode(' = ? AND ', $this->getTable()->getIdentifierColumnNames()) . ' = ?');
+ $this->clearRelated();
+ $record = $query->fetchOne($id);
+ } else {
+ // Use FETCH_ARRAY to avoid clearing object relations
+ $record = $this->getTable()->find($id, IPF_ORM::HYDRATE_ARRAY);
+ if ($record) {
+ $this->hydrate($record);
+ }
+ }
+
+ if ($record === false) {
+ throw new IPF_ORM_Exception('Failed to refresh. Record does not exist.');
+ }
+
+ $this->_modified = array();
+
+ $this->prepareIdentifiers();
+
+ $this->_state = IPF_ORM_Record::STATE_CLEAN;
+
+ return $this;
+ }
+
+ public function refreshRelated($name = null)
+ {
+ if (is_null($name)) {
+ foreach ($this->_table->getRelations() as $rel) {
+ $this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this);
+ }
+ } else {
+ $rel = $this->_table->getRelation($name);
+ $this->_references[$name] = $rel->fetchRelatedFor($this);
+ }
+ }
+
+ public function clearRelated()
+ {
+ $this->_references = array();
+ }
+
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ public function getData()
+ {
+ return $this->_data;
+ }
+
+ public function rawGet($fieldName)
+ {
+ if ( ! isset($this->_data[$fieldName])) {
+ throw new IPF_ORM_Exception('Unknown property '. $fieldName);
+ }
+ if ($this->_data[$fieldName] === self::$_null) {
+ return null;
+ }
+
+ return $this->_data[$fieldName];
+ }
+
+ public function load()
+ {
+ // only load the data from database if the IPF_ORM_Record is in proxy state
+ if ($this->_state == IPF_ORM_Record::STATE_PROXY) {
+ $this->refresh();
+ $this->_state = IPF_ORM_Record::STATE_CLEAN;
+ return true;
+ }
+ return false;
+ }
+
+ public function get($fieldName, $load = true)
+ {
+ $value = self::$_null;
+
+ if (isset($this->_data[$fieldName])) {
+ // check if the value is the IPF_ORM_Null object located in self::$_null)
+ if ($this->_data[$fieldName] === self::$_null && $load) {
+ $this->load();
+ }
+ if ($this->_data[$fieldName] === self::$_null) {
+ $value = null;
+ } else {
+ $value = $this->_data[$fieldName];
+ }
+ return $value;
+ }
+
+ if (isset($this->_values[$fieldName])) {
+ return $this->_values[$fieldName];
+ }
+
+ try {
+ if ( ! isset($this->_references[$fieldName]) && $load) {
+ $rel = $this->_table->getRelation($fieldName);
+ $this->_references[$fieldName] = $rel->fetchRelatedFor($this);
+ }
+ return $this->_references[$fieldName];
+ } catch (IPF_ORM_Exception_Table $e) {
+ foreach ($this->_table->getFilters() as $filter) {
+ if (($value = $filter->filterGet($this, $fieldName, $value)) !== null) {
+ return $value;
+ }
+ }
+ }
+ }
+
+ public function mapValue($name, $value)
+ {
+ $this->_values[$name] = $value;
+ }
+
+ public function set($fieldName, $value, $load = true)
+ {
+ if (isset($this->_data[$fieldName])) {
+ $type = $this->_table->getTypeOf($fieldName);
+ if ($value instanceof IPF_ORM_Record) {
+ $id = $value->getIncremented();
+
+ if ($id !== null && $type !== 'object') {
+ $value = $id;
+ }
+ }
+
+ if ($load) {
+ $old = $this->get($fieldName, $load);
+ } else {
+ $old = $this->_data[$fieldName];
+ }
+
+ if ($this->_isValueModified($type, $old, $value)) {
+ if ($value === null) {
+ $value = self::$_null;
+ }
+
+ $this->_data[$fieldName] = $value;
+ $this->_modified[] = $fieldName;
+ switch ($this->_state) {
+ case IPF_ORM_Record::STATE_CLEAN:
+ $this->_state = IPF_ORM_Record::STATE_DIRTY;
+ break;
+ case IPF_ORM_Record::STATE_TCLEAN:
+ $this->_state = IPF_ORM_Record::STATE_TDIRTY;
+ break;
+ }
+ }
+ } else {
+ try {
+ $this->coreSetRelated($fieldName, $value);
+ } catch (IPF_ORM_Exception_Table $e) {
+ foreach ($this->_table->getFilters() as $filter) {
+ if (($value = $filter->filterSet($this, $fieldName, $value)) !== null) {
+ break;
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ protected function _isValueModified($type, $old, $new)
+ {
+ if ($type == 'boolean' && (is_bool($old) || is_numeric($old)) && (is_bool($new) || is_numeric($new)) && $old == $new) {
+ return false;
+ } else {
+ return $old !== $new;
+ }
+ }
+
+ public function coreSetRelated($name, $value)
+ {
+ $rel = $this->_table->getRelation($name);
+
+ if ($value === null) {
+ $value = self::$_null;
+ }
+
+ // one-to-many or one-to-one relation
+ if ($rel instanceof IPF_ORM_Relation_ForeignKey || $rel instanceof IPF_ORM_Relation_LocalKey) {
+ if ( ! $rel->isOneToOne()) {
+ // one-to-many relation found
+ if ( ! ($value instanceof IPF_ORM_Collection)) {
+ throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting one-to-many references.");
+ }
+ if (isset($this->_references[$name])) {
+ $this->_references[$name]->setData($value->getData());
+ return $this;
+ }
+ } else {
+ if ($value !== self::$_null) {
+ $relatedTable = $value->getTable();
+ $foreignFieldName = $relatedTable->getFieldName($rel->getForeign());
+ $localFieldName = $this->_table->getFieldName($rel->getLocal());
+
+ // one-to-one relation found
+ if ( ! ($value instanceof IPF_ORM_Record)) {
+ throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Record or IPF_ORM_Null when setting one-to-one references.");
+ }
+ if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+ if ( ! empty($foreignFieldName) && $foreignFieldName != $value->getTable()->getIdentifier()) {
+ $this->set($localFieldName, $value->rawGet($foreignFieldName), false);
+ } else {
+ $this->set($localFieldName, $value, false);
+ }
+ } else {
+ $value->set($foreignFieldName, $this, false);
+ }
+ }
+ }
+
+ } else if ($rel instanceof IPF_ORM_Relation_Association) {
+ // join table relation found
+ if ( ! ($value instanceof IPF_ORM_Collection)) {
+ throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting many-to-many references.");
+ }
+ }
+
+ $this->_references[$name] = $value;
+ }
+
+ public function contains($fieldName)
+ {
+ if (isset($this->_data[$fieldName])) {
+ // this also returns true if the field is a IPF_ORM_Null.
+ // imho this is not correct behavior.
+ return true;
+ }
+ if (isset($this->_id[$fieldName])) {
+ return true;
+ }
+ if (isset($this->_values[$fieldName])) {
+ return true;
+ }
+ if (isset($this->_references[$fieldName]) &&
+ $this->_references[$fieldName] !== self::$_null) {
+
+ return true;
+ }
+ return false;
+ }
+
+ public function __unset($name)
+ {
+ if (isset($this->_data[$name])) {
+ $this->_data[$name] = array();
+ } else if (isset($this->_references[$name])) {
+ if ($this->_references[$name] instanceof IPF_ORM_Record) {
+ $this->_pendingDeletes[] = $this->$name;
+ $this->_references[$name] = self::$_null;
+ } elseif ($this->_references[$name] instanceof IPF_ORM_Collection) {
+ $this->_pendingDeletes[] = $this->$name;
+ $this->_references[$name]->setData(array());
+ }
+ }
+ }
+
+ public function getPendingDeletes()
+ {
+ return $this->_pendingDeletes;
+ }
+
+ public function save(IPF_ORM_Connection $conn = null)
+ {
+ if ($conn === null) {
+ $conn = $this->_table->getConnection();
+ }
+ $conn->unitOfWork->saveGraph($this);
+ }
+
+ public function trySave(IPF_ORM_Connection $conn = null) {
+ try {
+ $this->save($conn);
+ return true;
+ } catch (IPF_ORM_Exception_Validator $ignored) {
+ return false;
+ }
+ }
+
+ public function replace(IPF_ORM_Connection $conn = null)
+ {
+ if ($conn === null) {
+ $conn = $this->_table->getConnection();
+ }
+
+ if ($this->exists()) {
+ return $this->save();
+ } else {
+ $identifier = (array) $this->getTable()->getIdentifier();
+ return $conn->replace($this->_table, $this->toArray(), $identifier);
+ }
+ }
+
+ public function getModified()
+ {
+ $a = array();
+
+ foreach ($this->_modified as $k => $v) {
+ $a[$v] = $this->_data[$v];
+ }
+ return $a;
+ }
+
+ public function modifiedFields()
+ {
+ $a = array();
+
+ foreach ($this->_modified as $k => $v) {
+ $a[$v] = $this->_data[$v];
+ }
+ return $a;
+ }
+
+ public function getPrepared(array $array = array())
+ {
+ $a = array();
+
+ if (empty($array)) {
+ $modifiedFields = $this->_modified;
+ }
+
+ foreach ($modifiedFields as $field) {
+ $type = $this->_table->getTypeOf($field);
+
+ if ($this->_data[$field] === self::$_null) {
+ $a[$field] = null;
+ continue;
+ }
+
+ switch ($type) {
+ case 'array':
+ case 'object':
+ $a[$field] = serialize($this->_data[$field]);
+ break;
+ case 'gzip':
+ $a[$field] = gzcompress($this->_data[$field],5);
+ break;
+ case 'boolean':
+ $a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]);
+ break;
+ case 'enum':
+ $a[$field] = $this->_table->enumIndex($field, $this->_data[$field]);
+ break;
+ default:
+ if ($this->_data[$field] instanceof IPF_ORM_Record) {
+ $a[$field] = $this->_data[$field]->getIncremented();
+ if ($a[$field] !== null) {
+ $this->_data[$field] = $a[$field];
+ }
+ } else {
+ $a[$field] = $this->_data[$field];
+ }
+ /** TODO:
+ if ($this->_data[$v] === null) {
+ throw new IPF_ORM_Record_Exception('Unexpected null value.');
+ }
+ */
+ }
+ }
+ $map = $this->_table->inheritanceMap;
+ foreach ($map as $k => $v) {
+ $k = $this->_table->getFieldName($k);
+ $old = $this->get($k, false);
+
+ if ((string) $old !== (string) $v || $old === null) {
+ $a[$k] = $v;
+ $this->_data[$k] = $v;
+ }
+ }
+
+ return $a;
+ }
+
+ public function count()
+ {
+ return count($this->_data);
+ }
+
+ public function columnCount()
+ {
+ return $this->count();
+ }
+
+ public function toArray($deep = true, $prefixKey = false)
+ {
+ if ($this->_state == self::STATE_LOCKED) {
+ return false;
+ }
+
+ $stateBeforeLock = $this->_state;
+ $this->_state = self::STATE_LOCKED;
+
+ $a = array();
+
+ foreach ($this as $column => $value) {
+ if ($value === self::$_null || is_object($value)) {
+ $value = null;
+ }
+
+ $a[$column] = $value;
+ }
+
+ if ($this->_table->getIdentifierType() == IPF_ORM::IDENTIFIER_AUTOINC) {
+ $i = $this->_table->getIdentifier();
+ $a[$i] = $this->getIncremented();
+ }
+
+ if ($deep) {
+ foreach ($this->_references as $key => $relation) {
+ if (! $relation instanceof IPF_ORM_Null) {
+ $a[$key] = $relation->toArray($deep, $prefixKey);
+ }
+ }
+ }
+
+ // [FIX] Prevent mapped IPF_ORM_Records from being displayed fully
+ foreach ($this->_values as $key => $value) {
+ if ($value instanceof IPF_ORM_Record) {
+ $a[$key] = $value->toArray($deep, $prefixKey);
+ } else {
+ $a[$key] = $value;
+ }
+ }
+
+ $this->_state = $stateBeforeLock;
+
+ return $a;
+ }
+
+ public function merge($data, $deep = true)
+ {
+ if ($data instanceof $this) {
+ $array = $data->toArray($deep);
+ } else if (is_array($data)) {
+ $array = $data;
+ }
+
+ return $this->fromArray($array, $deep);
+ }
+
+ public function fromArray(array $array, $deep = true)
+ {
+ $refresh = false;
+ foreach ($array as $key => $value) {
+ if ($key == '_identifier') {
+ $refresh = true;
+ $this->assignIdentifier((array) $value);
+ continue;
+ }
+
+ if ($deep && $this->getTable()->hasRelation($key)) {
+ $this->$key->fromArray($value, $deep);
+ } else if ($this->getTable()->hasField($key)) {
+ $this->set($key, $value);
+ }
+ }
+
+ if ($refresh) {
+ $this->refresh();
+ }
+ }
+
+ public function synchronizeWithArray(array $array, $deep = true)
+ {
+ $refresh = false;
+ foreach ($array as $key => $value) {
+ if ($key == '_identifier') {
+ $refresh = true;
+ $this->assignIdentifier((array) $value);
+ continue;
+ }
+
+ if ($deep && $this->getTable()->hasRelation($key)) {
+ $this->get($key)->synchronizeWithArray($value);
+ } else if ($this->getTable()->hasField($key)) {
+ $this->set($key, $value);
+ }
+ }
+
+ // eliminate relationships missing in the $array
+ foreach ($this->_references as $name => $obj) {
+ if ( ! isset($array[$name])) {
+ unset($this->$name);
+ }
+ }
+
+ if ($refresh) {
+ $this->refresh();
+ }
+ }
+
+ public function exportTo($type, $deep = true)
+ {
+ if ($type == 'array') {
+ return $this->toArray($deep);
+ } else {
+ return IPF_ORM_Parser::dump($this->toArray($deep, true), $type);
+ }
+ }
+
+ public function importFrom($type, $data)
+ {
+ if ($type == 'array') {
+ return $this->fromArray($data);
+ } else {
+ return $this->fromArray(IPF_ORM_Parser::load($data, $type));
+ }
+ }
+
+ public function exists()
+ {
+ return ($this->_state !== IPF_ORM_Record::STATE_TCLEAN &&
+ $this->_state !== IPF_ORM_Record::STATE_TDIRTY);
+ }
+
+ public function isModified()
+ {
+ return ($this->_state === IPF_ORM_Record::STATE_DIRTY ||
+ $this->_state === IPF_ORM_Record::STATE_TDIRTY);
+ }
+
+ public function hasRelation($fieldName)
+ {
+ if (isset($this->_data[$fieldName]) || isset($this->_id[$fieldName])) {
+ return true;
+ }
+ return $this->_table->hasRelation($fieldName);
+ }
+
+ public function getIterator()
+ {
+ return new IPF_ORM_Record_Iterator($this);
+ }
+
+ public function delete(IPF_ORM_Connection $conn = null)
+ {
+ if ($conn == null) {
+ $conn = $this->_table->getConnection();
+ }
+ return $conn->unitOfWork->delete($this);
+ }
+
+ public function copy($deep = false)
+ {
+ $data = $this->_data;
+
+ if ($this->_table->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC) {
+ $id = $this->_table->getIdentifier();
+
+ unset($data[$id]);
+ }
+
+ $ret = $this->_table->create($data);
+ $modified = array();
+
+ foreach ($data as $key => $val) {
+ if ( ! ($val instanceof IPF_ORM_Null)) {
+ $ret->_modified[] = $key;
+ }
+ }
+
+ if ($deep) {
+ foreach ($this->_references as $key => $value) {
+ if ($value instanceof IPF_ORM_Collection) {
+ foreach ($value as $record) {
+ $ret->{$key}[] = $record->copy($deep);
+ }
+ } else if($value instanceof IPF_ORM_Record) {
+ $ret->set($key, $value->copy($deep));
+ }
+ }
+ }
+
+ return $ret;
+ }
+
+ public function assignIdentifier($id = false)
+ {
+ if ($id === false) {
+ $this->_id = array();
+ $this->_data = $this->cleanData($this->_data);
+ $this->_state = IPF_ORM_Record::STATE_TCLEAN;
+ $this->_modified = array();
+ } elseif ($id === true) {
+ $this->prepareIdentifiers(true);
+ $this->_state = IPF_ORM_Record::STATE_CLEAN;
+ $this->_modified = array();
+ } else {
+ if (is_array($id)) {
+ foreach ($id as $fieldName => $value) {
+ $this->_id[$fieldName] = $value;
+ $this->_data[$fieldName] = $value;
+ }
+ } else {
+ $name = $this->_table->getIdentifier();
+ $this->_id[$name] = $id;
+ $this->_data[$name] = $id;
+ }
+ $this->_state = IPF_ORM_Record::STATE_CLEAN;
+ $this->_modified = array();
+ }
+ }
+
+ public function identifier()
+ {
+ return $this->_id;
+ }
+
+ final public function getIncremented()
+ {
+ $id = current($this->_id);
+ if ($id === false) {
+ return null;
+ }
+
+ return $id;
+ }
+
+ public function getLast()
+ {
+ return $this;
+ }
+
+ public function hasReference($name)
+ {
+ return isset($this->_references[$name]);
+ }
+
+ public function reference($name)
+ {
+ if (isset($this->_references[$name])) {
+ return $this->_references[$name];
+ }
+ }
+
+ public function obtainReference($name)
+ {
+ if (isset($this->_references[$name])) {
+ return $this->_references[$name];
+ }
+ throw new IPF_ORM_Exception("Unknown reference $name");
+ }
+
+ public function getReferences()
+ {
+ return $this->_references;
+ }
+
+ final public function setRelated($alias, IPF_ORM_Access $coll)
+ {
+ $this->_references[$alias] = $coll;
+ }
+
+ public function loadReference($name)
+ {
+ $rel = $this->_table->getRelation($name);
+ $this->_references[$name] = $rel->fetchRelatedFor($this);
+ }
+
+ public function call($callback, $column)
+ {
+ $args = func_get_args();
+ array_shift($args);
+
+ if (isset($args[0])) {
+ $fieldName = $args[0];
+ $args[0] = $this->get($fieldName);
+
+ $newvalue = call_user_func_array($callback, $args);
+
+ $this->_data[$fieldName] = $newvalue;
+ }
+ return $this;
+ }
+
+ public function getNode()
+ {
+ if ( ! $this->_table->isTree()) {
+ return false;
+ }
+
+ if ( ! isset($this->_node)) {
+ $this->_node = IPF_ORM_Node::factory($this,
+ $this->getTable()->getOption('treeImpl'),
+ $this->getTable()->getOption('treeOptions')
+ );
+ }
+
+ return $this->_node;
+ }
+
+ public function unshiftFilter(IPF_ORM_Record_Filter $filter)
+ {
+ return $this->_table->unshiftFilter($filter);
+ }
+
+ public function unlink($alias, $ids = array())
+ {
+ $ids = (array) $ids;
+
+ $q = new IPF_ORM_Query();
+
+ $rel = $this->getTable()->getRelation($alias);
+
+ if ($rel instanceof IPF_ORM_Relation_Association) {
+ $q->delete()
+ ->from($rel->getAssociationTable()->getComponentName())
+ ->where($rel->getLocal() . ' = ?', array_values($this->identifier()));
+
+ if (count($ids) > 0) {
+ $q->whereIn($rel->getForeign(), $ids);
+ }
+
+ $q->execute();
+
+ } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+ $q->update($rel->getTable()->getComponentName())
+ ->set($rel->getForeign(), '?', array(null))
+ ->addWhere($rel->getForeign() . ' = ?', array_values($this->identifier()));
+
+ if (count($ids) > 0) {
+ $q->whereIn($rel->getTable()->getIdentifier(), $ids);
+ }
+
+ $q->execute();
+ }
+ if (isset($this->_references[$alias])) {
+ foreach ($this->_references[$alias] as $k => $record) {
+ if (in_array(current($record->identifier()), $ids)) {
+ $this->_references[$alias]->remove($k);
+ }
+ }
+ $this->_references[$alias]->takeSnapshot();
+ }
+ return $this;
+ }
+
+ public function link($alias, $ids)
+ {
+ $ids = (array) $ids;
+
+ if ( ! count($ids)) {
+ return $this;
+ }
+
+ $identifier = array_values($this->identifier());
+ $identifier = array_shift($identifier);
+
+ $rel = $this->getTable()->getRelation($alias);
+
+ if ($rel instanceof IPF_ORM_Relation_Association) {
+
+ $modelClassName = $rel->getAssociationTable()->getComponentName();
+ $localFieldName = $rel->getLocalFieldName();
+ $localFieldDef = $rel->getAssociationTable()->getColumnDefinition($localFieldName);
+ if ($localFieldDef['type'] == 'integer') {
+ $identifier = (integer) $identifier;
+ }
+ $foreignFieldName = $rel->getForeignFieldName();
+ $foreignFieldDef = $rel->getAssociationTable()->getColumnDefinition($foreignFieldName);
+ if ($foreignFieldDef['type'] == 'integer') {
+ for ($i = 0; $i < count($ids); $i++) {
+ $ids[$i] = (integer) $ids[$i];
+ }
+ }
+ foreach ($ids as $id) {
+ $record = new $modelClassName;
+ $record[$localFieldName] = $identifier;
+ $record[$foreignFieldName] = $id;
+ $record->save();
+ }
+
+ } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+
+ $q = new IPF_ORM_Query();
+
+ $q->update($rel->getTable()->getComponentName())
+ ->set($rel->getForeign(), '?', array_values($this->identifier()));
+
+ if (count($ids) > 0) {
+ $q->whereIn($rel->getTable()->getIdentifier(), $ids);
+ }
+
+ $q->execute();
+
+ } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+
+ $q = new IPF_ORM_Query();
+
+ $q->update($this->getTable()->getComponentName())
+ ->set($rel->getLocalFieldName(), '?', $ids);
+
+ if (count($ids) > 0) {
+ $q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier()));
+ }
+
+ $q->execute();
+
+ }
+
+ return $this;
+ }
+
+ public function __call($method, $args)
+ {
+ if (($template = $this->_table->getMethodOwner($method)) !== false) {
+ $template->setInvoker($this);
+ return call_user_func_array(array($template, $method), $args);
+ }
+
+ foreach ($this->_table->getTemplates() as $template) {
+ if (method_exists($template, $method)) {
+ $template->setInvoker($this);
+ $this->_table->setMethodOwner($method, $template);
+
+ return call_user_func_array(array($template, $method), $args);
+ }
+ }
+
+ throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
+ }
+
+ public function deleteNode()
+ {
+ $this->getNode()->delete();
+ }
+
+ public function free($deep = false)
+ {
+ if ($this->_state != self::STATE_LOCKED) {
+ $this->_state = self::STATE_LOCKED;
+
+ $this->_table->getRepository()->evict($this->_oid);
+ $this->_table->removeRecord($this);
+ $this->_data = array();
+ $this->_id = array();
+
+ if ($deep) {
+ foreach ($this->_references as $name => $reference) {
+ if ( ! ($reference instanceof IPF_ORM_Null)) {
+ $reference->free($deep);
+ }
+ }
+ }
+
+ $this->_references = array();
+ }
+ }
+
+ public function toString()
+ {
+ return IPF_ORM::dump(get_object_vars($this));
+ }
+
+ public function __toString()
+ {
+ return (string) $this->_oid;
+ }
+
+ public function ModelAdmin(){
+ $cn = get_class($this);
+ if (isset(IPF_Admin_Model::$models[$cn]))
+ return IPF_Admin_Model::$models[$cn];
+ return null;
+ }
+
+ function SetFromFormData($cleaned_values)
+ {
+ foreach ($cleaned_values as $key=>$val) {
+ $this->$key = $val;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Record_Abstract extends IPF_ORM_Access
+{
+ protected $_table;
+
+ public function setTableDefinition()
+ {
+ }
+
+ public function setUp()
+ {
+ }
+
+
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ public function addListener($listener, $name = null)
+ {
+ $this->_table->addRecordListener($listener, $name = null);
+ return $this;
+ }
+
+ public function getListener()
+ {
+ return $this->_table->getRecordListener();
+ }
+
+ public function setListener($listener)
+ {
+ $this->_table->setRecordListener($listener);
+ return $this;
+ }
+
+ public function index($name, array $definition = array())
+ {
+ if ( ! $definition) {
+ return $this->_table->getIndex($name);
+ } else {
+ return $this->_table->addIndex($name, $definition);
+ }
+ }
+ public function setAttribute($attr, $value)
+ {
+ $this->_table->setAttribute($attr, $value);
+ }
+ public function setTableName($tableName)
+ {
+ $this->_table->setTableName($tableName);
+ }
+ public function setInheritanceMap($map)
+ {
+ $this->_table->setOption('inheritanceMap', $map);
+ }
+
+ public function setSubclasses($map)
+ {
+ if (isset($map[get_class($this)])) {
+ $this->_table->setOption('inheritanceMap', $map[get_class($this)]);
+ return;
+ }
+ $this->_table->setOption('subclasses', array_keys($map));
+ }
+
+ public function attribute($attr, $value)
+ {
+ if ($value == null) {
+ if (is_array($attr)) {
+ foreach ($attr as $k => $v) {
+ $this->_table->setAttribute($k, $v);
+ }
+ } else {
+ return $this->_table->getAttribute($attr);
+ }
+ } else {
+ $this->_table->setAttribute($attr, $value);
+ }
+ }
+
+ public function option($name, $value = null)
+ {
+ if ($value === null) {
+ if (is_array($name)) {
+ foreach ($name as $k => $v) {
+ $this->_table->setOption($k, $v);
+ }
+ } else {
+ return $this->_table->getOption($name);
+ }
+ } else {
+ $this->_table->setOption($name, $value);
+ }
+ }
+
+ public function ownsOne()
+ {
+ $this->_table->bind(func_get_args(), IPF_ORM_Relation::ONE_COMPOSITE);
+
+ return $this;
+ }
+
+ public function ownsMany()
+ {
+ $this->_table->bind(func_get_args(), IPF_ORM_Relation::MANY_COMPOSITE);
+ return $this;
+ }
+
+ public function hasOne()
+ {
+ $this->_table->bind(func_get_args(), IPF_ORM_Relation::ONE_AGGREGATE);
+ return $this;
+ }
+
+ public function hasMany()
+ {
+ $this->_table->bind(func_get_args(), IPF_ORM_Relation::MANY_AGGREGATE);
+ return $this;
+ }
+
+ public function hasColumn($name, $type, $length = 2147483647, $options = "")
+ {
+ $this->_table->setColumn($name, $type, $length, $options);
+ }
+
+ public function hasColumns(array $definitions)
+ {
+ foreach ($definitions as $name => $options) {
+ $this->hasColumn($name, $options['type'], $options['length'], $options);
+ }
+ }
+
+ public function loadTemplate($template, array $options = array())
+ {
+ $this->actAs($template, $options);
+ }
+
+ public function bindQueryParts(array $queryParts)
+ {
+ $this->_table->bindQueryParts($queryParts);
+ return $this;
+ }
+
+ public function loadGenerator(IPF_ORM_Record_Generator $generator)
+ {
+ $generator->initialize($this->_table);
+ $this->_table->addGenerator($generator, get_class($generator));
+ }
+
+ public function actAs($tpl, array $options = array())
+ {
+ if ( ! is_object($tpl)) {
+ $className = 'IPF_ORM_Template_' . $tpl;
+
+ if (class_exists($className, true)) {
+ $tpl = new $className($options);
+ } else if (class_exists($tpl, true)) {
+ $tpl = new $tpl($options);
+ } else {
+ throw new IPF_ORM_Record_Exception('Could not load behavior named: "' . $tpl . '"');
+ }
+ }
+
+ if ( ! ($tpl instanceof IPF_ORM_Template)) {
+ throw new IPF_ORM_Record_Exception('Loaded behavior class is not an istance of IPF_ORM_Template.');
+ }
+
+ $className = get_class($tpl);
+
+ $this->_table->addTemplate($className, $tpl);
+
+ $tpl->setTable($this->_table);
+ $tpl->setUp();
+ $tpl->setTableDefinition();
+
+ return $this;
+ }
+
+ public function check($constraint, $name = null)
+ {
+ if (is_array($constraint)) {
+ foreach ($constraint as $name => $def) {
+ $this->_table->addCheckConstraint($def, $name);
+ }
+ } else {
+ $this->_table->addCheckConstraint($constraint, $name);
+ }
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Record_Filter
+{
+ protected $_table;
+
+ public function setTable(IPF_ORM_Table $table)
+ {
+ $this->_table = $table;
+ }
+
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ abstract public function filterSet(IPF_ORM_Record $record, $name, $value);
+ abstract public function filterGet(IPF_ORM_Record $record, $name);
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Record_Filter_Compound extends IPF_ORM_Record_Filter
+{
+ protected $_aliases = array();
+
+ public function __construct(array $aliases)
+ {
+ $this->_aliases = $aliases;
+ }
+ public function init()
+ {
+ // check that all aliases exist
+ foreach ($this->_aliases as $alias) {
+ $this->_table->getRelation($alias);
+ }
+ }
+
+ public function filterSet(IPF_ORM_Record $record, $name, $value)
+ {
+ foreach ($this->_aliases as $alias) {
+ if ( ! $record->exists()) {
+ if (isset($record[$alias][$name])) {
+ $record[$alias][$name] = $value;
+
+ return $record;
+ }
+ } else {
+ // we do not want to execute N + 1 queries here, hence we cannot use get()
+ if (($ref = $record->reference($alias)) !== null) {
+ if (isset($ref[$name])) {
+ $ref[$name] = $value;
+ }
+
+ return $record;
+ }
+ }
+ }
+ }
+
+ public function filterGet(IPF_ORM_Record $record, $name)
+ {
+ foreach ($this->_aliases as $alias) {
+ if ( ! $record->exists()) {
+ if (isset($record[$alias][$name])) {
+ return $record[$alias][$name];
+ }
+ } else {
+ // we do not want to execute N + 1 queries here, hence we cannot use get()
+ if (($ref = $record->reference($alias)) !== null) {
+ if (isset($ref[$name])) {
+ return $ref[$name];
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Record_Filter_Standard extends IPF_ORM_Record_Filter
+{
+ public function filterSet(IPF_ORM_Record $record, $name, $value)
+ {
+ throw new IPF_ORM_Exception(sprintf('Unknown record property / related component "%s" on "%s"', $name, get_class($record)));
+ }
+
+ public function filterGet(IPF_ORM_Record $record, $name)
+ {
+ throw new IPF_ORM_Exception(sprintf('Unknown record property / related component "%s" on "%s"', $name, get_class($record)));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Record_Iterator extends ArrayIterator
+{
+ private $record;
+ private static $null;
+ public function __construct(IPF_ORM_Record $record)
+ {
+ $this->record = $record;
+ parent::__construct($record->getData());
+ }
+
+ public static function initNullObject(IPF_ORM_Null $null)
+ {
+ self::$null = $null;
+ }
+
+ public function current()
+ {
+ $value = parent::current();
+
+ if ($value === self::$null) {
+ return null;
+ } else {
+ return $value;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Record_Listener implements IPF_ORM_Record_Listener_Interface
+{
+ public function preSerialize(IPF_ORM_Event $event){}
+ public function postSerialize(IPF_ORM_Event $event){}
+ public function preUnserialize(IPF_ORM_Event $event){}
+ public function postUnserialize(IPF_ORM_Event $event){}
+ public function preDqlSelect(IPF_ORM_Event $event){}
+ public function preSave(IPF_ORM_Event $event){}
+ public function postSave(IPF_ORM_Event $event){}
+ public function preDqlDelete(IPF_ORM_Event $event){}
+ public function preDelete(IPF_ORM_Event $event){}
+ public function postDelete(IPF_ORM_Event $event){}
+ public function preDqlUpdate(IPF_ORM_Event $event){}
+ public function preUpdate(IPF_ORM_Event $event){}
+ public function postUpdate(IPF_ORM_Event $event){}
+ public function preInsert(IPF_ORM_Event $event){}
+ public function postInsert(IPF_ORM_Event $event){}
+ public function preHydrate(IPF_ORM_Event $event){}
+ public function postHydrate(IPF_ORM_Event $event){}
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Record_Listener_Chain extends IPF_ORM_Access implements IPF_ORM_Record_Listener_Interface
+{
+ protected $_listeners = array();
+
+ public function add($listener, $name = null)
+ {
+ if ( ! ($listener instanceof IPF_ORM_Record_Listener_Interface) &&
+ ! ($listener instanceof IPF_ORM_Overloadable)) {
+ throw new IPF_Exception_ORM("Couldn't add eventlistener. Record listeners should implement either IPF_ORM_Record_Listener_Interface or IPF_ORM_Overloadable");
+ }
+ if ($name === null) {
+ $this->_listeners[] = $listener;
+ } else {
+ $this->_listeners[$name] = $listener;
+ }
+ }
+
+ public function get($key)
+ {
+ if ( ! isset($this->_listeners[$key])) {
+ return null;
+ }
+ return $this->_listeners[$key];
+ }
+
+ public function set($key, $listener)
+ {
+ $this->_listeners[$key] = $listener;
+ }
+
+ public function preSerialize(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preSerialize($event);
+ }
+ }
+
+ public function postSerialize(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preSerialize($event);
+ }
+ }
+
+ public function preUnserialize(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preUnserialize($event);
+ }
+ }
+
+ public function postUnserialize(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->postUnserialize($event);
+ }
+ }
+
+ public function preDqlSelect(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preDqlSelect($event);
+ }
+ }
+
+ public function preSave(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preSave($event);
+ }
+ }
+
+ public function postSave(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->postSave($event);
+ }
+ }
+
+ public function preDqlDelete(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preDqlDelete($event);
+ }
+ }
+
+ public function preDelete(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preDelete($event);
+ }
+ }
+
+ public function postDelete(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->postDelete($event);
+ }
+ }
+
+ public function preDqlUpdate(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preDqlUpdate($event);
+ }
+ }
+
+ public function preUpdate(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preUpdate($event);
+ }
+ }
+
+ public function postUpdate(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->postUpdate($event);
+ }
+ }
+
+ public function preInsert(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preInsert($event);
+ }
+ }
+
+ public function postInsert(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->postInsert($event);
+ }
+ }
+
+ public function preHydrate(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->preHydrate($event);
+ }
+ }
+
+ public function postHydrate(IPF_ORM_Event $event)
+ {
+ foreach ($this->_listeners as $listener) {
+ $listener->postHydrate($event);
+ }
+ }
+}
--- /dev/null
+<?php
+
+interface IPF_ORM_Record_Listener_Interface
+{
+ public function preSerialize(IPF_ORM_Event $event);
+ public function postSerialize(IPF_ORM_Event $event);
+ public function preUnserialize(IPF_ORM_Event $event);
+ public function postUnserialize(IPF_ORM_Event $event);
+ public function preSave(IPF_ORM_Event $event);
+ public function postSave(IPF_ORM_Event $event);
+ public function preDelete(IPF_ORM_Event $event);
+ public function postDelete(IPF_ORM_Event $event);
+ public function preUpdate(IPF_ORM_Event $event);
+ public function postUpdate(IPF_ORM_Event $event);
+ public function preInsert(IPF_ORM_Event $event);
+ public function postInsert(IPF_ORM_Event $event);
+ public function preHydrate(IPF_ORM_Event $event);
+ public function postHydrate(IPF_ORM_Event $event);
+}
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Relation implements ArrayAccess
+{
+ const ONE_AGGREGATE = 0;
+ const ONE_COMPOSITE = 1;
+ const MANY_AGGREGATE = 2;
+ const MANY_COMPOSITE = 3;
+
+ const ONE = 0;
+ const MANY = 2;
+
+ protected $definition = array('alias' => true,
+ 'foreign' => true,
+ 'local' => true,
+ 'class' => true,
+ 'type' => true,
+ 'table' => true,
+ 'localTable' => true,
+ 'name' => null,
+ 'refTable' => null,
+ 'onDelete' => null,
+ 'onUpdate' => null,
+ 'deferred' => null,
+ 'deferrable' => null,
+ 'constraint' => null,
+ 'equal' => false,
+ 'cascade' => array(), // application-level cascades
+ 'owningSide' => false, // whether this is the owning side
+ );
+
+ public function __construct(array $definition)
+ {
+ $def = array();
+ foreach ($this->definition as $key => $val) {
+ if ( ! isset($definition[$key]) && $val) {
+ throw new IPF_ORM_Exception($key . ' is required!');
+ }
+ if (isset($definition[$key])) {
+ $def[$key] = $definition[$key];
+ } else {
+ $def[$key] = $this->definition[$key];
+ }
+ }
+ $this->definition = $def;
+ }
+
+ public function hasConstraint()
+ {
+ return ($this->definition['constraint'] ||
+ ($this->definition['onUpdate']) ||
+ ($this->definition['onDelete']));
+ }
+ public function isDeferred()
+ {
+ return $this->definition['deferred'];
+ }
+
+ public function isDeferrable()
+ {
+ return $this->definition['deferrable'];
+ }
+ public function isEqual()
+ {
+ return $this->definition['equal'];
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->definition[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ if (isset($this->definition[$offset])) {
+ return $this->definition[$offset];
+ }
+
+ return null;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if (isset($this->definition[$offset])) {
+ $this->definition[$offset] = $value;
+ }
+ }
+
+ public function offsetUnset($offset)
+ {
+ $this->definition[$offset] = false;
+ }
+
+ public function toArray()
+ {
+ return $this->definition;
+ }
+
+ final public function getAlias()
+ {
+ return $this->definition['alias'];
+ }
+
+ final public function getType()
+ {
+ return $this->definition['type'];
+ }
+
+ public function isCascadeDelete()
+ {
+ return in_array('delete', $this->definition['cascade']);
+ }
+
+ final public function getTable()
+ {
+ return IPF_ORM_Manager::getInstance()
+ ->getConnectionForComponent($this->definition['class'])
+ ->getTable($this->definition['class']);
+ }
+
+ final public function getClass()
+ {
+ return $this->definition['class'];
+ }
+
+ final public function getLocal()
+ {
+ return $this->definition['local'];
+ }
+
+ final public function getLocalFieldName()
+ {
+ return $this->definition['localTable']->getFieldName($this->definition['local']);
+ }
+
+ final public function getForeign()
+ {
+ return $this->definition['foreign'];
+ }
+
+ final public function getForeignFieldName()
+ {
+ return $this->definition['table']->getFieldName($this->definition['foreign']);
+ }
+
+ final public function isComposite()
+ {
+ return ($this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE ||
+ $this->definition['type'] == IPF_ORM_Relation::MANY_COMPOSITE);
+ }
+
+ final public function isOneToOne()
+ {
+ return ($this->definition['type'] == IPF_ORM_Relation::ONE_AGGREGATE ||
+ $this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE);
+ }
+
+ public function getRelationDql($count)
+ {
+ $component = $this->getTable()->getComponentName();
+
+ $dql = 'FROM ' . $component
+ . ' WHERE ' . $component . '.' . $this->definition['foreign']
+ . ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')';
+
+ return $dql;
+ }
+
+ abstract public function fetchRelatedFor(IPF_ORM_Record $record);
+
+ public function __toString()
+ {
+ $r[] = "<pre>";
+ foreach ($this->definition as $k => $v) {
+ if (is_object($v)) {
+ $v = 'Object(' . get_class($v) . ')';
+ }
+ $r[] = $k . ' : ' . $v;
+ }
+ $r[] = "</pre>";
+ return implode("\n", $r);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Relation_Association extends IPF_ORM_Relation
+{
+ public function getAssociationFactory()
+ {
+ return $this->definition['refTable'];
+ }
+ public function getAssociationTable()
+ {
+ return $this->definition['refTable'];
+ }
+
+ public function getRelationDql($count, $context = 'record')
+ {
+ $table = $this->definition['refTable'];
+ $component = $this->definition['refTable']->getComponentName();
+
+ switch ($context) {
+ case "record":
+ $sub = substr(str_repeat("?, ", $count),0,-2);
+ $dql = 'FROM ' . $this->getTable()->getComponentName();
+ $dql .= '.' . $component;
+ $dql .= ' WHERE ' . $this->getTable()->getComponentName()
+ . '.' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
+ break;
+ case "collection":
+ $sub = substr(str_repeat("?, ", $count),0,-2);
+ $dql = 'FROM ' . $component . '.' . $this->getTable()->getComponentName();
+ $dql .= ' WHERE ' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
+ break;
+ }
+
+ return $dql;
+ }
+
+ public function fetchRelatedFor(IPF_ORM_Record $record)
+ {
+ $id = $record->getIncremented();
+ if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+ $coll = new IPF_ORM_Collection($this->getTable());
+ } else {
+ $coll = $this->getTable()->getConnection()->query($this->getRelationDql(1), array($id));
+ }
+ $coll->setReference($record, $this);
+ return $coll;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Relation_ForeignKey extends IPF_ORM_Relation
+{
+ public function fetchRelatedFor(IPF_ORM_Record $record)
+ {
+ $id = array();
+ $localTable = $record->getTable();
+ foreach ((array) $this->definition['local'] as $local) {
+ $value = $record->get($localTable->getFieldName($local));
+ if (isset($value)) {
+ $id[] = $value;
+ }
+ }
+ if ($this->isOneToOne()) {
+ if ( ! $record->exists() || empty($id) ||
+ ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+
+ $related = $this->getTable()->create();
+ } else {
+ $dql = 'FROM ' . $this->getTable()->getComponentName()
+ . ' WHERE ' . $this->getCondition();
+
+ $coll = $this->getTable()->getConnection()->query($dql, $id);
+ $related = $coll[0];
+ }
+
+ $related->set($related->getTable()->getFieldName($this->definition['foreign']),
+ $record, false);
+ } else {
+
+ if ( ! $record->exists() || empty($id) ||
+ ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+
+ $related = new IPF_ORM_Collection($this->getTable());
+ } else {
+ $query = $this->getRelationDql(1);
+ $related = $this->getTable()->getConnection()->query($query, $id);
+ }
+ $related->setReference($record, $this);
+ }
+ return $related;
+ }
+
+ public function getCondition($alias = null)
+ {
+ if ( ! $alias) {
+ $alias = $this->getTable()->getComponentName();
+ }
+ $conditions = array();
+ foreach ((array) $this->definition['foreign'] as $foreign) {
+ $conditions[] = $alias . '.' . $foreign . ' = ?';
+ }
+ return implode(' AND ', $conditions);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Relation_LocalKey extends IPF_ORM_Relation
+{
+ public function fetchRelatedFor(IPF_ORM_Record $record)
+ {
+ $localFieldName = $record->getTable()->getFieldName($this->definition['local']);
+ $id = $record->get($localFieldName);
+
+ if (is_null($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+ $related = $this->getTable()->create();
+ } else {
+ $dql = 'FROM ' . $this->getTable()->getComponentName()
+ . ' WHERE ' . $this->getCondition();
+
+ $related = $this->getTable()
+ ->getConnection()
+ ->query($dql, array($id))
+ ->getFirst();
+
+ if ( ! $related || empty($related)) {
+ $related = $this->getTable()->create();
+ }
+ }
+
+ $record->set($localFieldName, $related, false);
+
+ return $related;
+ }
+
+ public function getCondition($alias = null)
+ {
+ if ( ! $alias) {
+ $alias = $this->getTable()->getComponentName();
+ }
+ return $alias . '.' . $this->definition['foreign'] . ' = ?';
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Relation_Nest extends IPF_ORM_Relation_Association
+{
+ public function getRelationDql($count, $context = 'record')
+ {
+ switch ($context) {
+ case 'record':
+ $identifierColumnNames = $this->definition['table']->getIdentifierColumnNames();
+ $identifier = array_pop($identifierColumnNames);
+ $sub = 'SELECT '.$this->definition['foreign']
+ . ' FROM '.$this->definition['refTable']->getTableName()
+ . ' WHERE '.$this->definition['local']
+ . ' = ?';
+
+ $sub2 = 'SELECT '.$this->definition['local']
+ . ' FROM '.$this->definition['refTable']->getTableName()
+ . ' WHERE '.$this->definition['foreign']
+ . ' = ?';
+
+ $dql = 'FROM ' . $this->definition['table']->getComponentName()
+ . '.' . $this->definition['refTable']->getComponentName()
+ . ' WHERE ' . $this->definition['table']->getComponentName()
+ . '.' . $identifier
+ . ' IN (' . $sub . ')'
+ . ' || ' . $this->definition['table']->getComponentName()
+ . '.' . $identifier
+ . ' IN (' . $sub2 . ')';
+ break;
+ case 'collection':
+ $sub = substr(str_repeat('?, ', $count),0,-2);
+ $dql = 'FROM '.$this->definition['refTable']->getComponentName()
+ . '.' . $this->definition['table']->getComponentName()
+ . ' WHERE '.$this->definition['refTable']->getComponentName()
+ . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
+ };
+
+ return $dql;
+ }
+
+ public function fetchRelatedFor(IPF_ORM_Record $record)
+ {
+ $id = $record->getIncremented();
+
+
+ if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+ return new IPF_ORM_Collection($this->getTable());
+ } else {
+ $q = new IPF_ORM_RawSql($this->getTable()->getConnection());
+
+ $assocTable = $this->getAssociationFactory()->getTableName();
+ $tableName = $record->getTable()->getTableName();
+ $identifierColumnNames = $record->getTable()->getIdentifierColumnNames();
+ $identifier = array_pop($identifierColumnNames);
+
+ $sub = 'SELECT ' . $this->getForeign()
+ . ' FROM ' . $assocTable
+ . ' WHERE ' . $this->getLocal()
+ . ' = ?';
+
+ $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub . ')';
+ $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getForeign();
+
+ if ($this->definition['equal']) {
+ $sub2 = 'SELECT ' . $this->getLocal()
+ . ' FROM ' . $assocTable
+ . ' WHERE ' . $this->getForeign()
+ . ' = ?';
+
+ $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub2 . ')';
+ $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getLocal();
+ }
+ $q->select('{'.$tableName.'.*}, {'.$assocTable.'.*}')
+ ->from($tableName . ' INNER JOIN ' . $assocTable . ' ON ' . implode(' OR ', $joinCondition))
+ ->where(implode(' OR ', $condition));
+ $q->addComponent($tableName, $record->getTable()->getComponentName());
+ $q->addComponent($assocTable, $record->getTable()->getComponentName(). '.' . $this->getAssociationFactory()->getComponentName());
+
+ $params = ($this->definition['equal']) ? array($id, $id) : array($id);
+
+ return $q->execute($params);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Relation_Parser
+{
+ protected $_table;
+ protected $_relations = array();
+ protected $_pending = array();
+
+ public function __construct(IPF_ORM_Table $table)
+ {
+ $this->_table = $table;
+ }
+
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ public function getPendingRelation($name)
+ {
+ if ( ! isset($this->_pending[$name])) {
+ throw new IPF_ORM_Exception('Unknown pending relation ' . $name);
+ }
+
+ return $this->_pending[$name];
+ }
+
+ public function getPendingRelations()
+ {
+ return $this->_pending;
+ }
+
+ public function unsetPendingRelations($name)
+ {
+ unset($this->_pending[$name]);
+ }
+
+ public function hasRelation($name)
+ {
+ if ( ! isset($this->_pending[$name]) && ! isset($this->_relations[$name])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function bind($name, $options = array())
+ {
+ $e = explode(' as ', $name);
+ $name = $e[0];
+ $alias = isset($e[1]) ? $e[1] : $name;
+
+ if ( ! isset($options['type'])) {
+ throw new IPF_ORM_Exception('Relation type not set.');
+ }
+
+ if ($this->hasRelation($alias)) {
+ unset($this->relations[$alias]);
+ unset($this->_pending[$alias]);
+ }
+
+ $this->_pending[$alias] = array_merge($options, array('class' => $name, 'alias' => $alias));
+
+ return $this->_pending[$alias];
+ }
+
+ public function getRelation($alias, $recursive = true)
+ {
+ if (isset($this->_relations[$alias])) {
+ return $this->_relations[$alias];
+ }
+
+ if (isset($this->_pending[$alias])) {
+ $def = $this->_pending[$alias];
+ $identifierColumnNames = $this->_table->getIdentifierColumnNames();
+ $idColumnName = array_pop($identifierColumnNames);
+
+ // check if reference class name exists
+ // if it does we are dealing with association relation
+ if (isset($def['refClass'])) {
+ $def = $this->completeAssocDefinition($def);
+ $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
+
+ if ( ! isset($this->_pending[$def['refClass']]) &&
+ ! isset($this->_relations[$def['refClass']])) {
+
+ $parser = $def['refTable']->getRelationParser();
+ if ( ! $parser->hasRelation($this->_table->getComponentName())) {
+ $parser->bind($this->_table->getComponentName(),
+ array('type' => IPF_ORM_Relation::ONE,
+ 'local' => $def['local'],
+ 'foreign' => $idColumnName,
+ 'localKey' => true,
+ ));
+ }
+
+ if ( ! $this->hasRelation($def['refClass'])) {
+ $this->bind($def['refClass'], array('type' => IPF_ORM_Relation::MANY,
+ 'foreign' => $def['local'],
+ 'local' => $idColumnName));
+ }
+ }
+ if (in_array($def['class'], $localClasses)) {
+ $rel = new IPF_ORM_Relation_Nest($def);
+ } else {
+ $rel = new IPF_ORM_Relation_Association($def);
+ }
+ } else {
+ // simple foreign key relation
+ $def = $this->completeDefinition($def);
+
+ if (isset($def['localKey'])) {
+ $rel = new IPF_ORM_Relation_LocalKey($def);
+
+ // Automatically index foreign keys which are not primary
+ $foreign = (array) $def['foreign'];
+ foreach ($foreign as $fk) {
+ if ( ! $rel->getTable()->isIdentifier($fk)) {
+ $rel->getTable()->addIndex($fk, array('fields' => array($fk)));
+ }
+ }
+ } else {
+ $rel = new IPF_ORM_Relation_ForeignKey($def);
+ }
+ }
+ if (isset($rel)) {
+ // unset pending relation
+ unset($this->_pending[$alias]);
+
+ $this->_relations[$alias] = $rel;
+ return $rel;
+ }
+ }
+ if ($recursive) {
+ $this->getRelations();
+
+ return $this->getRelation($alias, false);
+ } else {
+ throw new IPF_ORM_Exception('Unknown relation alias ' . $alias);
+ }
+ }
+
+ public function getRelations()
+ {
+ foreach ($this->_pending as $k => $v) {
+ $this->getRelation($k);
+ }
+
+ return $this->_relations;
+ }
+
+ public function getImpl($template)
+ {
+ $conn = $this->_table->getConnection();
+
+ if (in_array('IPF_ORM_Template', class_parents($template))) {
+ $impl = $this->_table->getImpl($template);
+
+ if ($impl === null) {
+ throw new IPF_ORM_Exception("Couldn't find concrete implementation for template " . $template);
+ }
+ } else {
+ $impl = $template;
+ }
+
+ return $conn->getTable($impl);
+ }
+
+ public function completeAssocDefinition($def)
+ {
+ $conn = $this->_table->getConnection();
+ $def['table'] = $this->getImpl($def['class']);
+ $def['localTable'] = $this->_table;
+ $def['class'] = $def['table']->getComponentName();
+ $def['refTable'] = $this->getImpl($def['refClass']);
+
+ $id = $def['refTable']->getIdentifierColumnNames();
+
+ if (count($id) > 1) {
+ if ( ! isset($def['foreign'])) {
+ // foreign key not set
+ // try to guess the foreign key
+
+ $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0];
+ }
+ if ( ! isset($def['local'])) {
+ // foreign key not set
+ // try to guess the foreign key
+
+ $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0];
+ }
+ } else {
+
+ if ( ! isset($def['foreign'])) {
+ // foreign key not set
+ // try to guess the foreign key
+
+ $columns = $this->getIdentifiers($def['table']);
+
+ $def['foreign'] = $columns;
+ }
+ if ( ! isset($def['local'])) {
+ // local key not set
+ // try to guess the local key
+ $columns = $this->getIdentifiers($this->_table);
+
+ $def['local'] = $columns;
+ }
+ }
+ return $def;
+ }
+
+ public function getIdentifiers(IPF_ORM_Table $table)
+ {
+ $componentNameToLower = strtolower($table->getComponentName());
+ if (is_array($table->getIdentifier())) {
+ $columns = array();
+ foreach ((array) $table->getIdentifierColumnNames() as $identColName) {
+ $columns[] = $componentNameToLower . '_' . $identColName;
+ }
+ } else {
+ $columns = $componentNameToLower . '_' . $table->getColumnName(
+ $table->getIdentifier());
+ }
+
+ return $columns;
+ }
+
+ public function guessColumns(array $classes, IPF_ORM_Table $foreignTable)
+ {
+ $conn = $this->_table->getConnection();
+
+ foreach ($classes as $class) {
+ try {
+ $table = $conn->getTable($class);
+ } catch (IPF_ORM_Exception $e) {
+ continue;
+ }
+ $columns = $this->getIdentifiers($table);
+ $found = true;
+
+ foreach ((array) $columns as $column) {
+ if ( ! $foreignTable->hasColumn($column)) {
+ $found = false;
+ break;
+ }
+ }
+ if ($found) {
+ break;
+ }
+ }
+
+ if ( ! $found) {
+ throw new IPF_ORM_Exception("Couldn't find columns.");
+ }
+
+ return $columns;
+ }
+
+ public function completeDefinition($def)
+ {
+ $conn = $this->_table->getConnection();
+ $def['table'] = $this->getImpl($def['class']);
+ $def['localTable'] = $this->_table;
+ $def['class'] = $def['table']->getComponentName();
+
+ $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class']));
+ $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
+
+ $localIdentifierColumnNames = $this->_table->getIdentifierColumnNames();
+ $localIdentifierCount = count($localIdentifierColumnNames);
+ $localIdColumnName = array_pop($localIdentifierColumnNames);
+ $foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames();
+ $foreignIdColumnName = array_pop($foreignIdentifierColumnNames);
+
+ if (isset($def['local'])) {
+ if ( ! isset($def['foreign'])) {
+ // local key is set, but foreign key is not
+ // try to guess the foreign key
+
+ if ($def['local'] === $localIdColumnName) {
+ $def['foreign'] = $this->guessColumns($localClasses, $def['table']);
+ } else {
+ // the foreign field is likely to be the
+ // identifier of the foreign class
+ $def['foreign'] = $foreignIdColumnName;
+ $def['localKey'] = true;
+ }
+ } else {
+ if ($localIdentifierCount == 1) {
+ if ($def['local'] == $localIdColumnName && isset($def['owningSide'])
+ && $def['owningSide'] === true) {
+ $def['localKey'] = true;
+ } else if (($def['local'] !== $localIdColumnName && $def['type'] == IPF_ORM_Relation::ONE)) {
+ $def['localKey'] = true;
+ }
+ } else if ($localIdentifierCount > 1) {
+ // It's a composite key and since 'foreign' can not point to a composite
+ // key currently, we know that 'local' must be the foreign key.
+ $def['localKey'] = true;
+ }
+ }
+ } else {
+ if (isset($def['foreign'])) {
+ // local key not set, but foreign key is set
+ // try to guess the local key
+ if ($def['foreign'] === $foreignIdColumnName) {
+ $def['localKey'] = true;
+ try {
+ $def['local'] = $this->guessColumns($foreignClasses, $this->_table);
+ } catch (IPF_ORM_Exception $e) {
+ $def['local'] = $localIdColumnName;
+ }
+ } else {
+ $def['local'] = $localIdColumnName;
+ }
+ } else {
+ // neither local or foreign key is being set
+ // try to guess both keys
+
+ $conn = $this->_table->getConnection();
+
+ // the following loops are needed for covering inheritance
+ foreach ($localClasses as $class) {
+ $table = $conn->getTable($class);
+ $identifierColumnNames = $table->getIdentifierColumnNames();
+ $idColumnName = array_pop($identifierColumnNames);
+ $column = strtolower($table->getComponentName())
+ . '_' . $idColumnName;
+
+ foreach ($foreignClasses as $class2) {
+ $table2 = $conn->getTable($class2);
+ if ($table2->hasColumn($column)) {
+ $def['foreign'] = $column;
+ $def['local'] = $idColumnName;
+ return $def;
+ }
+ }
+ }
+
+ foreach ($foreignClasses as $class) {
+ $table = $conn->getTable($class);
+ $identifierColumnNames = $table->getIdentifierColumnNames();
+ $idColumnName = array_pop($identifierColumnNames);
+ $column = strtolower($table->getComponentName())
+ . '_' . $idColumnName;
+
+ foreach ($localClasses as $class2) {
+ $table2 = $conn->getTable($class2);
+ if ($table2->hasColumn($column)) {
+ $def['foreign'] = $idColumnName;
+ $def['local'] = $column;
+ $def['localKey'] = true;
+ return $def;
+ }
+ }
+ }
+
+ // auto-add columns and auto-build relation
+ $columns = array();
+ foreach ((array) $this->_table->getIdentifierColumnNames() as $id) {
+ // ?? should this not be $this->_table->getComponentName() ??
+ $column = strtolower($table->getComponentName())
+ . '_' . $id;
+
+ $col = $this->_table->getColumnDefinition($id);
+ $type = $col['type'];
+ $length = $col['length'];
+
+ unset($col['type']);
+ unset($col['length']);
+ unset($col['autoincrement']);
+ unset($col['sequence']);
+ unset($col['primary']);
+
+ $def['table']->setColumn($column, $type, $length, $col);
+
+ $columns[] = $column;
+ }
+ if (count($columns) > 1) {
+ $def['foreign'] = $columns;
+ } else {
+ $def['foreign'] = $columns[0];
+ }
+ $def['local'] = $localIdColumnName;
+ }
+ }
+ return $def;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Sequence extends IPF_ORM_Connection_Module
+{
+ public function nextId($seqName, $ondemand = true)
+ {
+ throw new IPF_ORM_Sequence_Exception('method not implemented');
+ }
+
+ public function lastInsertId($table = null, $field = null)
+ {
+ throw new IPF_ORM_Exception('method not implemented');
+ }
+
+ public function currId($seqName)
+ {
+ $this->warnings[] = 'database does not support getting current
+ sequence value, the sequence value was incremented';
+ return $this->nextId($seqName);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Sequence_Mysql extends IPF_ORM_Sequence
+{
+ public function nextId($seqName, $onDemand = true)
+ {
+ $sequenceName = $this->conn->quoteIdentifier($seqName, true);
+ $seqcolName = $this->conn->quoteIdentifier($this->conn->getAttribute(IPF_ORM::ATTR_SEQCOL_NAME), true);
+ $query = 'INSERT INTO ' . $sequenceName . ' (' . $seqcolName . ') VALUES (NULL)';
+
+ try {
+
+ $this->conn->exec($query);
+
+ } catch(IPF_ORM_Exception $e) {
+ if ($onDemand && $e->getPortableCode() == IPF_ORM::ERR_NOSUCHTABLE) {
+ // Since we are creating the sequence on demand
+ // we know the first id = 1 so initialize the
+ // sequence at 2
+ try {
+ $result = $this->conn->export->createSequence($seqName, 2);
+ } catch(IPF_ORM_Exception $e) {
+ throw new IPF_ORM_Exception('on demand sequence ' . $seqName . ' could not be created');
+ }
+ // First ID of a newly created sequence is 1
+ return 1;
+ }
+ throw $e;
+ }
+
+ $value = $this->lastInsertId();
+
+ if (is_numeric($value)) {
+ $query = 'DELETE FROM ' . $sequenceName . ' WHERE ' . $seqcolName . ' < ' . $value;
+ $this->conn->exec($query);
+ }
+ return $value;
+ }
+
+ public function lastInsertId($table = null, $field = null)
+ {
+ return $this->conn->getDbh()->lastInsertId();
+ }
+
+ public function currId($seqName)
+ {
+ $sequenceName = $this->conn->quoteIdentifier($seqName, true);
+ $seqcolName = $this->conn->quoteIdentifier($this->conn->getAttribute(IPF_ORM::ATTR_SEQCOL_NAME), true);
+ $query = 'SELECT MAX(' . $seqcolName . ') FROM ' . $sequenceName;
+ return (int) $this->conn->fetchOne($query);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Table extends IPF_ORM_Configurable implements Countable
+{
+ protected $_data = array();
+ protected $_identifier = array();
+ protected $_identifierType;
+ protected $_conn;
+ protected $_identityMap = array();
+
+ protected $_repository;
+ protected $_columns = array();
+ protected $_fieldNames = array();
+
+ protected $_columnNames = array();
+
+ protected $columnCount;
+
+ protected $hasDefaultValues;
+
+ protected $_options = array('name' => null,
+ 'tableName' => null,
+ 'sequenceName' => null,
+ 'inheritanceMap' => array(),
+ 'enumMap' => array(),
+ 'type' => null,
+ 'charset' => null,
+ 'collation' => null,
+ 'treeImpl' => null,
+ 'treeOptions' => array(),
+ 'indexes' => array(),
+ 'parents' => array(),
+ 'joinedParents' => array(),
+ 'queryParts' => array(),
+ 'versioning' => null,
+ 'subclasses' => array(),
+ );
+
+ protected $_tree;
+ protected $_parser;
+
+ protected $_templates = array();
+ protected $_filters = array();
+ protected $_generators = array();
+ protected $_invokedMethods = array();
+ protected $record;
+
+ public function __construct($name, IPF_ORM_Connection $conn, $initDefinition = false)
+ {
+ $this->_conn = $conn;
+
+ $this->setParent($this->_conn);
+
+ $this->_options['name'] = $name;
+ $this->_parser = new IPF_ORM_Relation_Parser($this);
+
+ if ($initDefinition) {
+ $this->record = $this->initDefinition();
+
+ $this->initIdentifier();
+
+ $this->record->setUp();
+
+ // if tree, set up tree
+ if ($this->isTree()) {
+ $this->getTree()->setUp();
+ }
+ } else {
+ if ( ! isset($this->_options['tableName'])) {
+ $this->setTableName(IPF_ORM_Inflector::tableize($this->_options['name']));
+ }
+ }
+ $this->_filters[] = new IPF_ORM_Record_Filter_Standard();
+ $this->_repository = new IPF_ORM_Table_Repository($this);
+ }
+
+ public function initDefinition()
+ {
+ $name = $this->_options['name'];
+ if ( ! class_exists($name) || empty($name)) {
+ throw new IPF_ORM_Exception("Couldn't find class " . $name);
+ }
+ $record = new $name($this);
+
+ $names = array();
+
+ $class = $name;
+
+ // get parent classes
+
+ do {
+ if ($class === 'IPF_ORM_Record') {
+ break;
+ }
+
+ $name = $class;
+ $names[] = $name;
+ } while ($class = get_parent_class($class));
+
+ if ($class === false) {
+ throw new IPF_ORM_Exception('Class "' . $name . '" must be a child class of IPF_ORM_Record');
+ }
+
+ // reverse names
+ $names = array_reverse($names);
+ // save parents
+ array_pop($names);
+ $this->_options['parents'] = $names;
+
+ // create database table
+ if (method_exists($record, 'setTableDefinition')) {
+ $record->setTableDefinition();
+ // get the declaring class of setTableDefinition method
+ $method = new ReflectionMethod($this->_options['name'], 'setTableDefinition');
+ $class = $method->getDeclaringClass();
+
+ } else {
+ $class = new ReflectionClass($class);
+ }
+
+ $this->_options['joinedParents'] = array();
+
+ foreach (array_reverse($this->_options['parents']) as $parent) {
+
+ if ($parent === $class->getName()) {
+ continue;
+ }
+ $ref = new ReflectionClass($parent);
+
+ if ($ref->isAbstract()) {
+ continue;
+ }
+ $parentTable = $this->_conn->getTable($parent);
+
+ $found = false;
+ $parentColumns = $parentTable->getColumns();
+
+ foreach ($parentColumns as $columnName => $definition) {
+ if ( ! isset($definition['primary']) || $definition['primary'] === false) {
+ if (isset($this->_columns[$columnName])) {
+ $found = true;
+ break;
+ } else {
+ if ( ! isset($parentColumns[$columnName]['owner'])) {
+ $parentColumns[$columnName]['owner'] = $parentTable->getComponentName();
+ }
+
+ $this->_options['joinedParents'][] = $parentColumns[$columnName]['owner'];
+ }
+ } else {
+ unset($parentColumns[$columnName]);
+ }
+ }
+
+ if ($found) {
+ continue;
+ }
+
+ foreach ($parentColumns as $columnName => $definition) {
+ $fullName = $columnName . ' as ' . $parentTable->getFieldName($columnName);
+ $this->setColumn($fullName, $definition['type'], $definition['length'], $definition, true);
+ }
+
+ break;
+ }
+
+ $this->_options['joinedParents'] = array_values(array_unique($this->_options['joinedParents']));
+
+ $this->_options['declaringClass'] = $class;
+
+ // set the table definition for the given tree implementation
+ if ($this->isTree()) {
+ $this->getTree()->setTableDefinition();
+ }
+
+ $this->columnCount = count($this->_columns);
+
+ if ( ! isset($this->_options['tableName'])) {
+ $this->setTableName(IPF_ORM_Inflector::tableize($class->getName()));
+ }
+
+ return $record;
+ }
+
+ public function initIdentifier()
+ {
+ switch (count($this->_identifier)) {
+ case 0:
+ if ( ! empty($this->_options['joinedParents'])) {
+ $root = current($this->_options['joinedParents']);
+
+ $table = $this->_conn->getTable($root);
+
+ $this->_identifier = $table->getIdentifier();
+
+ $this->_identifierType = ($table->getIdentifierType() !== IPF_ORM::IDENTIFIER_AUTOINC)
+ ? $table->getIdentifierType() : IPF_ORM::IDENTIFIER_NATURAL;
+
+ // add all inherited primary keys
+ foreach ((array) $this->_identifier as $id) {
+ $definition = $table->getDefinitionOf($id);
+
+ // inherited primary keys shouldn't contain autoinc
+ // and sequence definitions
+ unset($definition['autoincrement']);
+ unset($definition['sequence']);
+
+ // add the inherited primary key column
+ $fullName = $id . ' as ' . $table->getFieldName($id);
+ $this->setColumn($fullName, $definition['type'], $definition['length'],
+ $definition, true);
+ }
+ } else {
+ $definition = array('type' => 'integer',
+ 'length' => 20,
+ 'autoincrement' => true,
+ 'primary' => true);
+ $this->setColumn('id', $definition['type'], $definition['length'], $definition, true);
+ $this->_identifier = 'id';
+ $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC;
+ }
+ $this->columnCount++;
+ break;
+ case 1:
+ foreach ($this->_identifier as $pk) {
+ $e = $this->getDefinitionOf($pk);
+
+ $found = false;
+
+ foreach ($e as $option => $value) {
+ if ($found) {
+ break;
+ }
+
+ $e2 = explode(':', $option);
+
+ switch (strtolower($e2[0])) {
+ case 'autoincrement':
+ case 'autoinc':
+ if ($value !== false) {
+ $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC;
+ $found = true;
+ }
+ break;
+ case 'seq':
+ case 'sequence':
+ $this->_identifierType = IPF_ORM::IDENTIFIER_SEQUENCE;
+ $found = true;
+
+ if (is_string($value)) {
+ $this->_options['sequenceName'] = $value;
+ } else {
+ if (($sequence = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_SEQUENCE)) !== null) {
+ $this->_options['sequenceName'] = $sequence;
+ } else {
+ $this->_options['sequenceName'] = $this->_conn->formatter->getSequenceName($this->_options['tableName']);
+ }
+ }
+ break;
+ }
+ }
+ if ( ! isset($this->_identifierType)) {
+ $this->_identifierType = IPF_ORM::IDENTIFIER_NATURAL;
+ }
+ }
+
+ $this->_identifier = $pk;
+
+ break;
+ default:
+ $this->_identifierType = IPF_ORM::IDENTIFIER_COMPOSITE;
+ }
+ }
+
+ public function getColumnOwner($columnName)
+ {
+ if (isset($this->_columns[$columnName]['owner'])) {
+ return $this->_columns[$columnName]['owner'];
+ } else {
+ return $this->getComponentName();
+ }
+ }
+
+ public function getRecordInstance()
+ {
+ if ( ! $this->record) {
+ $this->record = new $this->_options['name'];
+ }
+ return $this->record;
+ }
+
+ public function isInheritedColumn($columnName)
+ {
+ return (isset($this->_columns[$columnName]['owner']));
+ }
+
+ public function isIdentifier($fieldName)
+ {
+ return ($fieldName === $this->getIdentifier() ||
+ in_array($fieldName, (array) $this->getIdentifier()));
+ }
+
+ public function isIdentifierAutoincrement()
+ {
+ return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC;
+ }
+
+ public function isIdentifierComposite()
+ {
+ return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_COMPOSITE;
+ }
+
+ public function getMethodOwner($method)
+ {
+ return (isset($this->_invokedMethods[$method])) ?
+ $this->_invokedMethods[$method] : false;
+ }
+
+ public function setMethodOwner($method, $class)
+ {
+ $this->_invokedMethods[$method] = $class;
+ }
+
+ public function getTemplates()
+ {
+ return $this->_templates;
+ }
+
+ public function export()
+ {
+ $this->_conn->export->exportTable($this);
+ }
+
+ public function getExportableFormat($parseForeignKeys = true)
+ {
+ $columns = array();
+ $primary = array();
+
+ foreach ($this->getColumns() as $name => $definition) {
+
+ if (isset($definition['owner'])) {
+ continue;
+ }
+
+ switch ($definition['type']) {
+ case 'enum':
+ if (isset($definition['default'])) {
+ $definition['default'] = $this->enumIndex($name, $definition['default']);
+ }
+ break;
+ case 'boolean':
+ if (isset($definition['default'])) {
+ $definition['default'] = $this->getConnection()->convertBooleans($definition['default']);
+ }
+ break;
+ }
+ $columns[$name] = $definition;
+
+ if (isset($definition['primary']) && $definition['primary']) {
+ $primary[] = $name;
+ }
+ }
+
+ $options['foreignKeys'] = isset($this->_options['foreignKeys']) ?
+ $this->_options['foreignKeys'] : array();
+
+ if ($parseForeignKeys && $this->getAttribute(IPF_ORM::ATTR_EXPORT)
+ & IPF_ORM::EXPORT_CONSTRAINTS) {
+
+ $constraints = array();
+
+ $emptyIntegrity = array('onUpdate' => null,
+ 'onDelete' => null);
+
+ foreach ($this->getRelations() as $name => $relation) {
+ $fk = $relation->toArray();
+ $fk['foreignTable'] = $relation->getTable()->getTableName();
+
+ if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) {
+ if ($relation->hasConstraint()) {
+ throw new IPF_ORM_Exception("Badly constructed integrity constraints.");
+ }
+ continue;
+ }
+
+ $integrity = array('onUpdate' => $fk['onUpdate'],
+ 'onDelete' => $fk['onDelete']);
+
+ if ($relation instanceof IPF_ORM_Relation_LocalKey) {
+ $def = array('local' => $relation->getLocal(),
+ 'foreign' => $relation->getForeign(),
+ 'foreignTable' => $relation->getTable()->getTableName());
+
+ if (($key = array_search($def, $options['foreignKeys'])) === false) {
+ $options['foreignKeys'][] = $def;
+ $constraints[] = $integrity;
+ } else {
+ if ($integrity !== $emptyIntegrity) {
+ $constraints[$key] = $integrity;
+ }
+ }
+ }
+ }
+
+ foreach ($constraints as $k => $def) {
+ $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def);
+ }
+ }
+
+ $options['primary'] = $primary;
+
+ return array('tableName' => $this->getOption('tableName'),
+ 'columns' => $columns,
+ 'options' => array_merge($this->getOptions(), $options));
+ }
+
+ public function getRelationParser()
+ {
+ return $this->_parser;
+ }
+
+ public function __get($option)
+ {
+ if (isset($this->_options[$option])) {
+ return $this->_options[$option];
+ }
+ return null;
+ }
+
+ public function __isset($option)
+ {
+ return isset($this->_options[$option]);
+ }
+
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ public function setOptions($options)
+ {
+ foreach ($options as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+
+ public function addForeignKey(array $definition)
+ {
+ $this->_options['foreignKeys'][] = $definition;
+ }
+
+ public function addCheckConstraint($definition, $name)
+ {
+ if (is_string($name)) {
+ $this->_options['checks'][$name] = $definition;
+ } else {
+ $this->_options['checks'][] = $definition;
+ }
+
+ return $this;
+ }
+
+ public function addIndex($index, array $definition)
+ {
+ $this->_options['indexes'][$index] = $definition;
+ }
+
+ public function getIndex($index)
+ {
+ if (isset($this->_options['indexes'][$index])) {
+ return $this->_options['indexes'][$index];
+ }
+
+ return false;
+ }
+
+ public function bind($args, $type)
+ {
+ $options = array();
+ $options['type'] = $type;
+
+ if ( ! isset($args[1])) {
+ $args[1] = array();
+ }
+
+ // the following is needed for backwards compatibility
+ if (is_string($args[1])) {
+ if ( ! isset($args[2])) {
+ $args[2] = array();
+ } elseif (is_string($args[2])) {
+ $args[2] = (array) $args[2];
+ }
+
+ $classes = array_merge($this->_options['parents'], array($this->getComponentName()));
+
+ $e = explode('.', $args[1]);
+ if (in_array($e[0], $classes)) {
+ if ($options['type'] >= IPF_ORM_Relation::MANY) {
+ $options['foreign'] = $e[1];
+ } else {
+ $options['local'] = $e[1];
+ }
+ } else {
+ $e2 = explode(' as ', $args[0]);
+ if ($e[0] !== $e2[0] && ( ! isset($e2[1]) || $e[0] !== $e2[1])) {
+ $options['refClass'] = $e[0];
+ }
+
+ $options['foreign'] = $e[1];
+ }
+
+ $options = array_merge($args[2], $options);
+ } else {
+ $options = array_merge($args[1], $options);
+ }
+
+ $this->_parser->bind($args[0], $options);
+ }
+
+ public function hasRelation($alias)
+ {
+ return $this->_parser->hasRelation($alias);
+ }
+
+ public function getRelation($alias, $recursive = true)
+ {
+ return $this->_parser->getRelation($alias, $recursive);
+ }
+
+ public function getRelations()
+ {
+ return $this->_parser->getRelations();
+ }
+
+ public function createQuery($alias = '')
+ {
+ if ( ! empty($alias)) {
+ $alias = ' ' . trim($alias);
+ }
+ return IPF_ORM_Query::create($this->_conn)->from($this->getComponentName() . $alias);
+ }
+
+ public function getRepository()
+ {
+ return $this->_repository;
+ }
+
+ public function setOption($name, $value)
+ {
+ switch ($name) {
+ case 'name':
+ case 'tableName':
+ break;
+ case 'enumMap':
+ case 'inheritanceMap':
+ case 'index':
+ case 'treeOptions':
+ if ( ! is_array($value)) {
+ throw new IPF_ORM_Exception($name . ' should be an array.');
+ }
+ break;
+ }
+ $this->_options[$name] = $value;
+ }
+
+ public function getOption($name)
+ {
+ if (isset($this->_options[$name])) {
+ return $this->_options[$name];
+ }
+ return null;
+ }
+
+ public function getColumnName($fieldName)
+ {
+ // FIX ME: This is being used in places where an array is passed, but it should not be an array
+ // For example in places where IPF_ORM should support composite foreign/primary keys
+ $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName;
+
+ if (isset($this->_columnNames[$fieldName])) {
+ return $this->_columnNames[$fieldName];
+ }
+
+ return strtolower($fieldName);
+ }
+
+ public function getColumnDefinition($columnName)
+ {
+ if ( ! isset($this->_columns[$columnName])) {
+ return false;
+ }
+ return $this->_columns[$columnName];
+ }
+
+ public function getFieldName($columnName)
+ {
+ if (isset($this->_fieldNames[$columnName])) {
+ return $this->_fieldNames[$columnName];
+ }
+ return $columnName;
+ }
+ public function setColumns(array $definitions)
+ {
+ foreach ($definitions as $name => $options) {
+ $this->setColumn($name, $options['type'], $options['length'], $options);
+ }
+ }
+
+ public function setColumn($name, $type, $length = null, $options = array(), $prepend = false)
+ {
+ if (is_string($options)) {
+ $options = explode('|', $options);
+ }
+
+ foreach ($options as $k => $option) {
+ if (is_numeric($k)) {
+ if ( ! empty($option)) {
+ $options[$option] = true;
+ }
+ unset($options[$k]);
+ }
+ }
+
+ // extract column name & field name
+ if (stripos($name, ' as '))
+ {
+ if (strpos($name, ' as')) {
+ $parts = explode(' as ', $name);
+ } else {
+ $parts = explode(' AS ', $name);
+ }
+
+ if (count($parts) > 1) {
+ $fieldName = $parts[1];
+ } else {
+ $fieldName = $parts[0];
+ }
+
+ $name = strtolower($parts[0]);
+ } else {
+ $fieldName = $name;
+ $name = strtolower($name);
+ }
+
+ $name = trim($name);
+ $fieldName = trim($fieldName);
+
+ if ($prepend) {
+ $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames);
+ $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames);
+ } else {
+ $this->_columnNames[$fieldName] = $name;
+ $this->_fieldNames[$name] = $fieldName;
+ }
+
+ if ($length == null) {
+ switch ($type) {
+ case 'string':
+ case 'clob':
+ case 'float':
+ case 'integer':
+ case 'array':
+ case 'object':
+ case 'blob':
+ case 'gzip':
+ // use php int max
+ $length = 2147483647;
+ break;
+ case 'boolean':
+ $length = 1;
+ case 'date':
+ // YYYY-MM-DD ISO 8601
+ $length = 10;
+ case 'time':
+ // HH:NN:SS+00:00 ISO 8601
+ $length = 14;
+ case 'timestamp':
+ // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
+ $length = 25;
+ break;
+ }
+ }
+
+ $options['type'] = $type;
+ $options['length'] = $length;
+
+ if ($prepend) {
+ $this->_columns = array_merge(array($name => $options), $this->_columns);
+ } else {
+ $this->_columns[$name] = $options;
+ }
+
+ if (isset($options['primary']) && $options['primary']) {
+ if (isset($this->_identifier)) {
+ $this->_identifier = (array) $this->_identifier;
+ }
+ if ( ! in_array($fieldName, $this->_identifier)) {
+ $this->_identifier[] = $fieldName;
+ }
+ }
+ if (isset($options['default'])) {
+ $this->hasDefaultValues = true;
+ }
+ }
+
+ public function hasDefaultValues()
+ {
+ return $this->hasDefaultValues;
+ }
+
+ public function getDefaultValueOf($fieldName)
+ {
+ $columnName = $this->getColumnName($fieldName);
+ if ( ! isset($this->_columns[$columnName])) {
+ throw new IPF_ORM_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
+ }
+ if (isset($this->_columns[$columnName]['default'])) {
+ return $this->_columns[$columnName]['default'];
+ } else {
+ return null;
+ }
+ }
+
+ public function getIdentifier()
+ {
+ return $this->_identifier;
+ }
+
+ public function getIdentifierType()
+ {
+ return $this->_identifierType;
+ }
+
+ public function hasColumn($columnName)
+ {
+ return isset($this->_columns[strtolower($columnName)]);
+ }
+
+ public function hasField($fieldName)
+ {
+ return isset($this->_columnNames[$fieldName]);
+ }
+
+ public function setConnection(IPF_ORM_Connection $conn)
+ {
+ $this->_conn = $conn;
+
+ $this->setParent($this->_conn);
+
+ return $this;
+ }
+
+ public function getConnection()
+ {
+ return $this->_conn;
+ }
+
+ public function create(array $array = array())
+ {
+ $record = new $this->_options['name']($this, true);
+ $record->fromArray($array);
+
+ return $record;
+ }
+
+ public function find($id, $hydrationMode = null)
+ {
+ if (is_null($id)) {
+ return false;
+ }
+
+ $id = is_array($id) ? array_values($id) : array($id);
+
+ $q = $this->createQuery();
+ $q->where(implode(' = ? AND ', (array) $this->getIdentifier()) . ' = ?', $id)
+ ->limit(1);
+ $res = $q->fetchOne(array(), $hydrationMode);
+ $q->free();
+
+ return $res;
+ }
+
+ public function findAll($hydrationMode = null)
+ {
+ return $this->createQuery()->execute(array(), $hydrationMode);
+ }
+
+ public function findBySql($dql, $params = array(), $hydrationMode = null)
+ {
+ return $this->createQuery()->where($dql)->execute($params, $hydrationMode);
+ }
+
+ public function findByDql($dql, $params = array(), $hydrationMode = null)
+ {
+ $parser = new IPF_ORM_Query($this->_conn);
+ $component = $this->getComponentName();
+ $query = 'FROM ' . $component . ' WHERE ' . $dql;
+
+ return $parser->query($query, $params, $hydrationMode);
+ }
+
+ public function execute($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD)
+ {
+ return IPF_ORM_Manager::getInstance()
+ ->getQueryRegistry()
+ ->get($queryKey, $this->getComponentName())
+ ->execute($params, $hydrationMode);
+ }
+
+ public function executeOne($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD)
+ {
+ return IPF_ORM_Manager::getInstance()
+ ->getQueryRegistry()
+ ->get($queryKey, $this->getComponentName())
+ ->fetchOne($params, $hydrationMode);
+ }
+
+ public function clear()
+ {
+ $this->_identityMap = array();
+ }
+
+ public function addRecord(IPF_ORM_Record $record)
+ {
+ $id = implode(' ', $record->identifier());
+
+ if (isset($this->_identityMap[$id])) {
+ return false;
+ }
+
+ $this->_identityMap[$id] = $record;
+
+ return true;
+ }
+
+ public function removeRecord(IPF_ORM_Record $record)
+ {
+ $id = implode(' ', $record->identifier());
+
+ if (isset($this->_identityMap[$id])) {
+ unset($this->_identityMap[$id]);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function getRecord()
+ {
+ if ( ! empty($this->_data)) {
+ $identifierFieldNames = $this->getIdentifier();
+
+ if ( ! is_array($identifierFieldNames)) {
+ $identifierFieldNames = array($identifierFieldNames);
+ }
+
+ $found = false;
+ foreach ($identifierFieldNames as $fieldName) {
+ if ( ! isset($this->_data[$fieldName])) {
+ // primary key column not found return new record
+ $found = true;
+ break;
+ }
+ $id[] = $this->_data[$fieldName];
+ }
+
+ if ($found) {
+ $recordName = $this->getComponentName();
+ $record = new $recordName($this, true);
+ $this->_data = array();
+ return $record;
+ }
+
+
+ $id = implode(' ', $id);
+
+ if (isset($this->_identityMap[$id])) {
+ $record = $this->_identityMap[$id];
+ $record->hydrate($this->_data);
+ } else {
+ $recordName = $this->getComponentName();
+ $record = new $recordName($this);
+ $this->_identityMap[$id] = $record;
+ }
+ $this->_data = array();
+ } else {
+ $recordName = $this->getComponentName();
+ $record = new $recordName($this, true);
+ }
+
+ return $record;
+ }
+
+ public function getClassnameToReturn()
+ {
+ if ( ! isset($this->_options['subclasses'])) {
+ return $this->_options['name'];
+ }
+ foreach ($this->_options['subclasses'] as $subclass) {
+ $table = $this->_conn->getTable($subclass);
+ $inheritanceMap = $table->getOption('inheritanceMap');
+ $nomatch = false;
+ foreach ($inheritanceMap as $key => $value) {
+ if ( ! isset($this->_data[$key]) || $this->_data[$key] != $value) {
+ $nomatch = true;
+ break;
+ }
+ }
+ if ( ! $nomatch) {
+ return $table->getComponentName();
+ }
+ }
+ return $this->_options['name'];
+ }
+
+ final public function getProxy($id = null)
+ {
+ if ($id !== null) {
+ $identifierColumnNames = $this->getIdentifierColumnNames();
+ $query = 'SELECT ' . implode(', ', (array) $identifierColumnNames)
+ . ' FROM ' . $this->getTableName()
+ . ' WHERE ' . implode(' = ? && ', (array) $identifierColumnNames) . ' = ?';
+ $query = $this->applyInheritance($query);
+
+ $params = array_merge(array($id), array_values($this->_options['inheritanceMap']));
+
+ $this->_data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC);
+
+ if ($this->_data === false)
+ return false;
+ }
+ return $this->getRecord();
+ }
+
+ final public function applyInheritance($where)
+ {
+ if ( ! empty($this->_options['inheritanceMap'])) {
+ $a = array();
+ foreach ($this->_options['inheritanceMap'] as $field => $value) {
+ $a[] = $this->getColumnName($field) . ' = ?';
+ }
+ $i = implode(' AND ', $a);
+ $where .= ' AND ' . $i;
+ }
+ return $where;
+ }
+
+ public function count()
+ {
+ $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(IPF_ORM::FETCH_NUM);
+ return current($a);
+ }
+
+ public function getQueryObject()
+ {
+ $graph = new IPF_ORM_Query($this->getConnection());
+ $graph->load($this->getComponentName());
+ return $graph;
+ }
+
+ public function getEnumValues($fieldName)
+ {
+ $columnName = $this->getColumnName($fieldName);
+ if (isset($this->_columns[$columnName]['values'])) {
+ return $this->_columns[$columnName]['values'];
+ } else {
+ return array();
+ }
+ }
+
+ public function enumValue($fieldName, $index)
+ {
+ if ($index instanceof IPF_ORM_Null) {
+ return $index;
+ }
+
+ $columnName = $this->getColumnName($fieldName);
+ if ( ! $this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)
+ && isset($this->_columns[$columnName]['values'][$index])
+ ) {
+ return $this->_columns[$columnName]['values'][$index];
+ }
+
+ return $index;
+ }
+
+ public function enumIndex($fieldName, $value)
+ {
+ $values = $this->getEnumValues($fieldName);
+
+ $index = array_search($value, $values);
+ if ($index === false || !$this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+ return $index;
+ }
+ return $value;
+ }
+
+ public function validateField($fieldName, $value, IPF_ORM_Record $record = null)
+ {
+ if ($record instanceof IPF_ORM_Record) {
+ $errorStack = $record->getErrorStack();
+ } else {
+ $record = $this->create();
+ $errorStack = new IPF_ORM_Validator_ErrorStack($this->getOption('name'));
+ }
+
+ if ($value === self::$_null) {
+ $value = null;
+ } else if ($value instanceof IPF_ORM_Record) {
+ $value = $value->getIncremented();
+ }
+
+ $dataType = $this->getTypeOf($fieldName);
+
+ // Validate field type, if type validation is enabled
+ if ($this->getAttribute(IPF_ORM::ATTR_VALIDATE) & IPF_ORM::VALIDATE_TYPES) {
+ if ( ! IPF_ORM_Validator::isValidType($value, $dataType)) {
+ $errorStack->add($fieldName, 'type');
+ }
+ if ($dataType == 'enum') {
+ $enumIndex = $this->enumIndex($fieldName, $value);
+ if ($enumIndex === false) {
+ $errorStack->add($fieldName, 'enum');
+ }
+ }
+ }
+
+ // Validate field length, if length validation is enabled
+ if ($this->getAttribute(IPF_ORM::ATTR_VALIDATE) & IPF_ORM::VALIDATE_LENGTHS) {
+ if ( ! IPF_ORM_Validator::validateLength($value, $dataType, $this->getFieldLength($fieldName))) {
+ $errorStack->add($fieldName, 'length');
+ }
+ }
+
+ // Run all custom validators
+ foreach ($this->getFieldValidators($fieldName) as $validatorName => $args) {
+ if ( ! is_string($validatorName)) {
+ $validatorName = $args;
+ $args = array();
+ }
+
+ $validator = IPF_ORM_Validator::getValidator($validatorName);
+ $validator->invoker = $record;
+ $validator->field = $fieldName;
+ $validator->args = $args;
+ if ( ! $validator->validate($value)) {
+ $errorStack->add($fieldName, $validator);
+ }
+ }
+
+ return $errorStack;
+ }
+
+ public function getColumnCount()
+ {
+ return $this->columnCount;
+ }
+
+ public function getColumns()
+ {
+ return $this->_columns;
+ }
+
+ public function removeColumn($fieldName)
+ {
+ if ( ! $this->hasField($fieldName)) {
+ return false;
+ }
+
+ $columnName = $this->getColumnName($fieldName);
+ unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]);
+ $this->columnCount = count($this->_columns);
+ return true;
+ }
+
+ public function getColumnNames(array $fieldNames = null)
+ {
+ if ($fieldNames === null) {
+ return array_keys($this->_columns);
+ } else {
+ $columnNames = array();
+ foreach ($fieldNames as $fieldName) {
+ $columnNames[] = $this->getColumnName($fieldName);
+ }
+ return $columnNames;
+ }
+ }
+
+ public function getIdentifierColumnNames()
+ {
+ return $this->getColumnNames((array) $this->getIdentifier());
+ }
+
+ public function getFieldNames()
+ {
+ return array_values($this->_fieldNames);
+ }
+
+ public function getDefinitionOf($fieldName)
+ {
+ $columnName = $this->getColumnName($fieldName);
+ return $this->getColumnDefinition($columnName);
+ }
+
+ public function getTypeOf($fieldName)
+ {
+ return $this->getTypeOfColumn($this->getColumnName($fieldName));
+ }
+
+ public function getTypeOfColumn($columnName)
+ {
+ return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false;
+ }
+
+ public function setData(array $data)
+ {
+ $this->_data = $data;
+ }
+
+ public function getData()
+ {
+ return $this->_data;
+ }
+
+ public function prepareValue($fieldName, $value, $typeHint = null)
+ {
+ if ($value === self::$_null) {
+ return self::$_null;
+ } else if ($value === null) {
+ return null;
+ } else {
+ $type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint;
+
+ switch ($type) {
+ case 'integer':
+ case 'string';
+ // don't do any casting here PHP INT_MAX is smaller than what the databases support
+ break;
+ case 'enum':
+ return $this->enumValue($fieldName, $value);
+ break;
+ case 'boolean':
+ return (boolean) $value;
+ break;
+ case 'array':
+ case 'object':
+ if (is_string($value)) {
+ $value = empty($value) ? null:unserialize($value);
+
+ if ($value === false) {
+ throw new IPF_ORM_Exception('Unserialization of ' . $fieldName . ' failed.');
+ }
+ return $value;
+ }
+ break;
+ case 'gzip':
+ $value = gzuncompress($value);
+
+ if ($value === false) {
+ throw new IPF_ORM_Exception('Uncompressing of ' . $fieldName . ' failed.');
+ }
+ return $value;
+ break;
+ }
+ }
+ return $value;
+ }
+
+ public function getTree()
+ {
+ if (isset($this->_options['treeImpl'])) {
+ if ( ! $this->_tree) {
+ $options = isset($this->_options['treeOptions']) ? $this->_options['treeOptions'] : array();
+ $this->_tree = IPF_ORM_Tree::factory($this,
+ $this->_options['treeImpl'],
+ $options
+ );
+ }
+ return $this->_tree;
+ }
+ return false;
+ }
+
+ public function getComponentName()
+ {
+ return $this->_options['name'];
+ }
+
+ public function getTableName()
+ {
+ return $this->_options['tableName'];
+ }
+
+ public function setTableName($tableName)
+ {
+ $this->setOption('tableName', $this->_conn->formatter->getTableName($tableName));
+ }
+
+ public function isTree()
+ {
+ return ( ! is_null($this->_options['treeImpl'])) ? true : false;
+ }
+
+ public function getTemplate($template)
+ {
+ if ( ! isset($this->_templates[$template])) {
+ throw new IPF_ORM_Exception('Template ' . $template . ' not loaded');
+ }
+
+ return $this->_templates[$template];
+ }
+
+ public function hasTemplate($template)
+ {
+ return isset($this->_templates[$template]);
+ }
+
+ public function addTemplate($template, IPF_ORM_Template $impl)
+ {
+ $this->_templates[$template] = $impl;
+
+ return $this;
+ }
+
+ public function getGenerators()
+ {
+ return $this->_generators;
+ }
+
+ public function getGenerator($generator)
+ {
+ if ( ! isset($this->_generators[$generator])) {
+ throw new IPF_ORM_Exception('Generator ' . $generator . ' not loaded');
+ }
+
+ return $this->_generators[$generator];
+ }
+
+ public function hasGenerator($generator)
+ {
+ return isset($this->_generators[$generator]);
+ }
+
+ public function addGenerator(IPF_ORM_Record_Generator $generator, $name = null)
+ {
+ if ($name === null) {
+ $this->_generators[] = $generator;
+ } else {
+ $this->_generators[$name] = $generator;
+ }
+ return $this;
+ }
+
+ public function bindQueryParts(array $queryParts)
+ {
+ $this->_options['queryParts'] = $queryParts;
+
+ return $this;
+ }
+
+ public function bindQueryPart($queryPart, $value)
+ {
+ $this->_options['queryParts'][$queryPart] = $value;
+
+ return $this;
+ }
+
+ public function getFieldValidators($fieldName)
+ {
+ $validators = array();
+ $columnName = $this->getColumnName($fieldName);
+ // this loop is a dirty workaround to get the validators filtered out of
+ // the options, since everything is squeezed together currently
+ foreach ($this->_columns[$columnName] as $name => $args) {
+ if (empty($name)
+ || $name == 'primary'
+ || $name == 'protected'
+ || $name == 'autoincrement'
+ || $name == 'default'
+ || $name == 'values'
+ || $name == 'sequence'
+ || $name == 'zerofill'
+ || $name == 'owner'
+ || $name == 'scale'
+ || $name == 'type'
+ || $name == 'length'
+ || $name == 'fixed') {
+ continue;
+ }
+ if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])) {
+ continue;
+ }
+ // skip it if it's explicitly set to FALSE (i.e. notnull => false)
+ if ($args === false) {
+ continue;
+ }
+ $validators[$name] = $args;
+ }
+
+ return $validators;
+ }
+
+ public function getFieldLength($fieldName)
+ {
+ return $this->_columns[$this->getColumnName($fieldName)]['length'];
+ }
+
+ public function getBoundQueryPart($queryPart)
+ {
+ if ( ! isset($this->_options['queryParts'][$queryPart])) {
+ return null;
+ }
+
+ return $this->_options['queryParts'][$queryPart];
+ }
+
+ public function unshiftFilter(IPF_ORM_Record_Filter $filter)
+ {
+ $filter->setTable($this);
+
+ $filter->init();
+
+ array_unshift($this->_filters, $filter);
+
+ return $this;
+ }
+
+ public function getFilters()
+ {
+ return $this->_filters;
+ }
+
+ public function __toString()
+ {
+ return IPF_ORM_Utils::getTableAsString($this);
+ }
+
+ protected function findBy($fieldName, $value, $hydrationMode = null)
+ {
+ return $this->createQuery()->where($fieldName . ' = ?', array($value))->execute(array(), $hydrationMode);
+ }
+
+ protected function findOneBy($fieldName, $value, $hydrationMode = null)
+ {
+ $results = $this->createQuery()
+ ->where($fieldName . ' = ?',array($value))
+ ->limit(1)
+ ->execute(array(), $hydrationMode);
+
+ if (is_array($results) && isset($results[0])) {
+ return $results[0];
+ } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) {
+ return $results->getFirst();
+ } else {
+ return false;
+ }
+ }
+
+ protected function _resolveFindByFieldName($name)
+ {
+ $fieldName = IPF_ORM_Inflector::tableize($name);
+ if ($this->hasColumn($name) || $this->hasField($name)) {
+ return $this->getFieldName($this->getColumnName($name));
+ } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) {
+ return $this->getFieldName($this->getColumnName($fieldName));
+ } else {
+ return false;
+ }
+ }
+
+ public function __call($method, $arguments)
+ {
+ if (substr($method, 0, 6) == 'findBy') {
+ $by = substr($method, 6, strlen($method));
+ $method = 'findBy';
+ } else if (substr($method, 0, 9) == 'findOneBy') {
+ $by = substr($method, 9, strlen($method));
+ $method = 'findOneBy';
+ }
+
+ if (isset($by)) {
+ if ( ! isset($arguments[0])) {
+ throw new IPF_ORM_Exception('You must specify the value to findBy');
+ }
+
+ $fieldName = $this->_resolveFindByFieldName($by);
+ $hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
+ if ($this->hasField($fieldName)) {
+ return $this->$method($fieldName, $arguments[0], $hydrationMode);
+ } else if ($this->hasRelation($by)) {
+ $relation = $this->getRelation($by);
+
+ if ($relation['type'] === IPF_ORM_Relation::MANY) {
+ throw new IPF_ORM_Exception('Cannot findBy many relationship.');
+ }
+
+ return $this->$method($relation['local'], $arguments[0], $hydrationMode);
+ } else {
+ throw new IPF_ORM_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.');
+ }
+ }
+
+ throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Table_Repository implements Countable, IteratorAggregate
+{
+ private $table;
+ private $registry = array();
+
+ public function __construct(IPF_ORM_Table $table)
+ {
+ $this->table = $table;
+ }
+
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+ public function add(IPF_ORM_Record $record)
+ {
+ $oid = $record->getOID();
+
+ if (isset($this->registry[$oid])) {
+ return false;
+ }
+ $this->registry[$oid] = $record;
+
+ return true;
+ }
+
+ public function get($oid)
+ {
+ if ( ! isset($this->registry[$oid])) {
+ throw new IPF_ORM_Exception("Unknown object identifier");
+ }
+ return $this->registry[$oid];
+ }
+
+ public function count()
+ {
+ return count($this->registry);
+ }
+
+ public function evict($oid)
+ {
+ if ( ! isset($this->registry[$oid])) {
+ return false;
+ }
+ unset($this->registry[$oid]);
+ return true;
+ }
+
+ public function evictAll()
+ {
+ $evicted = 0;
+ foreach ($this->registry as $oid=>$record) {
+ if ($this->evict($oid)) {
+ $evicted++;
+ }
+ }
+ return $evicted;
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->registry);
+ }
+
+ public function contains($oid)
+ {
+ return isset($this->registry[$oid]);
+ }
+
+ public function loadAll()
+ {
+ $this->table->findAll();
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+abstract class IPF_ORM_Template extends IPF_ORM_Record_Abstract
+{
+ protected $_invoker;
+ protected $_plugin;
+ public function setTable(IPF_ORM_Table $table)
+ {
+ $this->_table = $table;
+ }
+
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ public function setInvoker(IPF_ORM_Record $invoker)
+ {
+ $this->_invoker = $invoker;
+ }
+
+ public function getInvoker()
+ {
+ return $this->_invoker;
+ }
+
+ public function addChild(IPF_ORM_Template $template)
+ {
+ $this->_plugin->addChild($template);
+
+ return $this;
+ }
+
+ public function getPlugin()
+ {
+ return $this->_plugin;
+ }
+
+ public function get($name)
+ {
+ throw new IPF_ORM_Exception("Templates doesn't support accessors.");
+ }
+
+ public function set($name, $value)
+ {
+ throw new IPF_ORM_Exception("Templates doesn't support accessors.");
+ }
+
+ public function setUp()
+ {
+
+ }
+
+ public function setTableDefinition()
+ {
+
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Template_Listener_Timestampable extends IPF_ORM_Record_Listener
+{
+ protected $_options = array();
+ public function __construct(array $options)
+ {
+ $this->_options = $options;
+ }
+
+ public function preInsert(IPF_ORM_Event $event)
+ {
+ if( ! $this->_options['created']['disabled']) {
+ $createdName = $this->_options['created']['name'];
+ $event->getInvoker()->$createdName = $this->getTimestamp('created');
+ }
+
+ if( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) {
+ $updatedName = $this->_options['updated']['name'];
+ $event->getInvoker()->$updatedName = $this->getTimestamp('updated');
+ }
+ }
+
+ public function preUpdate(IPF_ORM_Event $event)
+ {
+ if( ! $this->_options['updated']['disabled']) {
+ $updatedName = $this->_options['updated']['name'];
+ $event->getInvoker()->$updatedName = $this->getTimestamp('updated');
+ }
+ }
+
+ public function getTimestamp($type)
+ {
+ $options = $this->_options[$type];
+
+ if ($options['expression'] !== false && is_string($options['expression'])) {
+ return new IPF_ORM_Expression($options['expression']);
+ } else {
+ if ($options['type'] == 'date') {
+ return date($options['format'], time());
+ } else if ($options['type'] == 'timestamp') {
+ return date($options['format'], time());
+ } else {
+ return time();
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Template_Timestampable extends IPF_ORM_Template
+{
+ protected $_options = array('created' => array('name' => 'created_at',
+ 'type' => 'timestamp',
+ 'format' => 'Y-m-d H:i:s',
+ 'disabled' => false,
+ 'expression' => false,
+ 'options' => array()),
+ 'updated' => array('name' => 'updated_at',
+ 'type' => 'timestamp',
+ 'format' => 'Y-m-d H:i:s',
+ 'disabled' => false,
+ 'expression' => false,
+ 'onInsert' => true,
+ 'options' => array()));
+
+ public function __construct(array $options = array())
+ {
+ $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options);
+ }
+
+ public function setTableDefinition()
+ {
+ if( ! $this->_options['created']['disabled']) {
+ $this->hasColumn($this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options']);
+ }
+ if( ! $this->_options['updated']['disabled']) {
+ $this->hasColumn($this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options']);
+ }
+ $this->addListener(new IPF_ORM_Template_Listener_Timestampable($this->_options));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Transaction extends IPF_ORM_Connection_Module
+{
+ const STATE_SLEEP = 0;
+ const STATE_ACTIVE = 1;
+ const STATE_BUSY = 2;
+
+ protected $_nestingLevel = 0;
+ protected $_internalNestingLevel = 0;
+ protected $invalid = array();
+ protected $savePoints = array();
+ protected $_collections = array();
+
+ public function addCollection(IPF_ORM_Collection $coll)
+ {
+ $this->_collections[] = $coll;
+
+ return $this;
+ }
+
+ public function getState()
+ {
+ switch ($this->_nestingLevel) {
+ case 0:
+ return IPF_ORM_Transaction::STATE_SLEEP;
+ break;
+ case 1:
+ return IPF_ORM_Transaction::STATE_ACTIVE;
+ break;
+ default:
+ return IPF_ORM_Transaction::STATE_BUSY;
+ }
+ }
+
+ public function addInvalid(IPF_ORM_Record $record)
+ {
+ if (in_array($record, $this->invalid, true)) {
+ return false;
+ }
+ $this->invalid[] = $record;
+ return true;
+ }
+
+ public function getInvalid()
+ {
+ return $this->invalid;
+ }
+
+ public function getTransactionLevel()
+ {
+ return $this->_nestingLevel;
+ }
+
+ public function getInternalTransactionLevel()
+ {
+ return $this->_internalNestingLevel;
+ }
+
+ public function beginTransaction($savepoint = null)
+ {
+ $this->conn->connect();
+ $listener = $this->conn->getAttribute(IPF_ORM::ATTR_LISTENER);
+
+
+ if ( ! is_null($savepoint)) {
+ $this->savePoints[] = $savepoint;
+
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_CREATE);
+
+ $listener->preSavepointCreate($event);
+
+ if ( ! $event->skipOperation) {
+ $this->createSavePoint($savepoint);
+ }
+
+ $listener->postSavepointCreate($event);
+ } else {
+ if ($this->_nestingLevel == 0) {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_BEGIN);
+
+ $listener->preTransactionBegin($event);
+
+ if ( ! $event->skipOperation) {
+ try {
+ $this->_doBeginTransaction();
+ } catch (Exception $e) {
+ throw new IPF_ORM_Exception($e->getMessage());
+ }
+ }
+ $listener->postTransactionBegin($event);
+ }
+ }
+
+ $level = ++$this->_nestingLevel;
+
+ return $level;
+ }
+
+ public function commit($savepoint = null)
+ {
+ if ($this->_nestingLevel == 0) {
+ throw new IPF_ORM_Exception("Commit failed. There is no active transaction.");
+ }
+
+ $this->conn->connect();
+
+ $listener = $this->conn->getAttribute(IPF_ORM::ATTR_LISTENER);
+
+ if ( ! is_null($savepoint)) {
+ $this->_nestingLevel -= $this->removeSavePoints($savepoint);
+
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_COMMIT);
+
+ $listener->preSavepointCommit($event);
+
+ if ( ! $event->skipOperation) {
+ $this->releaseSavePoint($savepoint);
+ }
+
+ $listener->postSavepointCommit($event);
+ } else {
+
+ if ($this->_nestingLevel == 1 || $this->_internalNestingLevel == 1) {
+ if ( ! empty($this->invalid)) {
+ if ($this->_internalNestingLevel == 1) {
+ // transaction was started by IPF_ORM, so we are responsible
+ // for a rollback
+ $this->rollback();
+ $tmp = $this->invalid;
+ $this->invalid = array();
+ throw new IPF_ORM_Exception_Validator($tmp);
+ }
+ }
+ if ($this->_nestingLevel == 1) {
+ // take snapshots of all collections used within this transaction
+ foreach ($this->_collections as $coll) {
+ $coll->takeSnapshot();
+ }
+ $this->_collections = array();
+
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_COMMIT);
+
+ $listener->preTransactionCommit($event);
+ if ( ! $event->skipOperation) {
+ $this->_doCommit();
+ }
+ $listener->postTransactionCommit($event);
+ }
+ }
+
+ if ($this->_nestingLevel > 0) {
+ $this->_nestingLevel--;
+ }
+ if ($this->_internalNestingLevel > 0) {
+ $this->_internalNestingLevel--;
+ }
+ }
+
+ return true;
+ }
+
+ public function rollback($savepoint = null)
+ {
+ if ($this->_nestingLevel == 0) {
+ throw new IPF_ORM_Exception("Rollback failed. There is no active transaction.");
+ }
+
+ $this->conn->connect();
+
+ if ($this->_internalNestingLevel > 1 || $this->_nestingLevel > 1) {
+ $this->_internalNestingLevel--;
+ $this->_nestingLevel--;
+ return false;
+ }
+
+ $listener = $this->conn->getAttribute(IPF_ORM::ATTR_LISTENER);
+
+ if ( ! is_null($savepoint)) {
+ $this->_nestingLevel -= $this->removeSavePoints($savepoint);
+
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_ROLLBACK);
+
+ $listener->preSavepointRollback($event);
+
+ if ( ! $event->skipOperation) {
+ $this->rollbackSavePoint($savepoint);
+ }
+
+ $listener->postSavepointRollback($event);
+ } else {
+ $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_ROLLBACK);
+
+ $listener->preTransactionRollback($event);
+
+ if ( ! $event->skipOperation) {
+ $this->_nestingLevel = 0;
+ $this->_internalNestingLevel = 0;
+ try {
+ $this->_doRollback();
+ } catch (Exception $e) {
+ throw new IPF_ORM_Exception($e->getMessage());
+ }
+ }
+
+ $listener->postTransactionRollback($event);
+ }
+
+ return true;
+ }
+
+ protected function createSavePoint($savepoint)
+ {
+ throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
+ }
+
+ protected function releaseSavePoint($savepoint)
+ {
+ throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
+ }
+
+ protected function rollbackSavePoint($savepoint)
+ {
+ throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
+ }
+
+ protected function _doRollback()
+ {
+ $this->conn->getDbh()->rollback();
+ }
+
+ protected function _doCommit()
+ {
+ $this->conn->getDbh()->commit();
+ }
+
+ protected function _doBeginTransaction()
+ {
+ $this->conn->getDbh()->beginTransaction();
+ }
+
+ private function removeSavePoints($savepoint)
+ {
+ $this->savePoints = array_values($this->savePoints);
+
+ $found = false;
+ $i = 0;
+
+ foreach ($this->savePoints as $key => $sp) {
+ if ( ! $found) {
+ if ($sp === $savepoint) {
+ $found = true;
+ }
+ }
+ if ($found) {
+ $i++;
+ unset($this->savePoints[$key]);
+ }
+ }
+
+ return $i;
+ }
+
+ public function setIsolation($isolation)
+ {
+ throw new IPF_ORM_Exception('Transaction isolation levels not supported by this driver.');
+ }
+
+ public function getIsolation()
+ {
+ throw new IPF_ORM_Exception('Fetching transaction isolation level not supported by this driver.');
+ }
+
+ public function beginInternalTransaction($savepoint = null)
+ {
+ $this->_internalNestingLevel++;
+ return $this->beginTransaction($savepoint);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Transaction_Mysql extends IPF_ORM_Transaction
+{
+ protected function createSavePoint($savepoint)
+ {
+ $query = 'SAVEPOINT ' . $savepoint;
+
+ return $this->conn->execute($query);
+ }
+
+ protected function releaseSavePoint($savepoint)
+ {
+ $query = 'RELEASE SAVEPOINT ' . $savepoint;
+
+ return $this->conn->execute($query);
+ }
+
+ protected function rollbackSavePoint($savepoint)
+ {
+ $query = 'ROLLBACK TO SAVEPOINT ' . $savepoint;
+
+ return $this->conn->execute($query);
+ }
+
+ public function setIsolation($isolation)
+ {
+ switch ($isolation) {
+ case 'READ UNCOMMITTED':
+ case 'READ COMMITTED':
+ case 'REPEATABLE READ':
+ case 'SERIALIZABLE':
+ break;
+ default:
+ throw new IPF_ORM_Exception('Isolation level ' . $isolation . ' is not supported.');
+ }
+
+ $query = 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $isolation;
+
+ return $this->conn->execute($query);
+ }
+
+ public function getIsolation()
+ {
+ return $this->conn->fetchOne('SELECT @@tx_isolation');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Utils {
+
+ public static function getConnectionStateAsString($state)
+ {
+ switch ($state) {
+ case IPF_ORM_Transaction::STATE_SLEEP:
+ return "open";
+ break;
+ case IPF_ORM_Transaction::STATE_BUSY:
+ return "busy";
+ break;
+ case IPF_ORM_Transaction::STATE_ACTIVE:
+ return "active";
+ break;
+ }
+ }
+
+ public static function getConnectionAsString(IPF_ORM_Connection $connection)
+ {
+ $r[] = '<pre>';
+ $r[] = 'IPF_ORM_Connection object';
+ $r[] = 'State : ' . IPF_ORM_Utils::getConnectionStateAsString($connection->transaction->getState());
+ $r[] = 'Open Transactions : ' . $connection->transaction->getTransactionLevel();
+ $r[] = 'Table in memory : ' . $connection->count();
+ $r[] = 'Driver name : ' . $connection->getAttribute(IPF_ORM::ATTR_DRIVER_NAME);
+ $r[] = "</pre>";
+ return implode("\n",$r)."<br>";
+ }
+
+ public static function getValidators()
+ {
+ return array(
+ 'country',
+ 'creditcard',
+ 'date',
+ 'driver',
+ 'email',
+ 'exception',
+ 'future',
+ 'htmlcolor',
+ 'ip',
+ 'minlength',
+ 'nospace',
+ 'notblank',
+ 'notnull',
+ 'past',
+ 'range',
+ 'readonly',
+ 'regexp',
+ 'time',
+ 'timestamp',
+ 'unique',
+ 'unsigned',
+ 'usstate',
+ );
+ }
+
+
+ public static function arrayDeepMerge()
+ {
+ switch (func_num_args()) {
+ case 0:
+ return false;
+ case 1:
+ return func_get_arg(0);
+ case 2:
+ $args = func_get_args();
+ $args[2] = array();
+
+ if (is_array($args[0]) && is_array($args[1]))
+ {
+ foreach (array_unique(array_merge(array_keys($args[0]),array_keys($args[1]))) as $key)
+ {
+ $isKey0 = array_key_exists($key, $args[0]);
+ $isKey1 = array_key_exists($key, $args[1]);
+
+ if ($isKey0 && $isKey1 && is_array($args[0][$key]) && is_array($args[1][$key]))
+ {
+ $args[2][$key] = self::arrayDeepMerge($args[0][$key], $args[1][$key]);
+ } else if ($isKey0 && $isKey1) {
+ $args[2][$key] = $args[1][$key];
+ } else if ( ! $isKey1) {
+ $args[2][$key] = $args[0][$key];
+ } else if ( ! $isKey0) {
+ $args[2][$key] = $args[1][$key];
+ }
+ }
+
+ return $args[2];
+ } else {
+ return $args[1];
+ }
+ default:
+ $args = func_get_args();
+ $args[1] = self::arrayDeepMerge($args[0], $args[1]);
+ array_shift($args);
+ return call_user_func_array(array('IPF', 'arrayDeepMerge'), $args);
+ break;
+ }
+ }
+
+ public static function makeDirectories($path, $mode = 0777)
+ {
+ if ( ! $path) {
+ return false;
+ }
+
+ if (is_dir($path) || is_file($path)) {
+ return true;
+ }
+
+ return mkdir(trim($path), $mode, true);
+ }
+
+ public static function removeDirectories($folderPath)
+ {
+ if (is_dir($folderPath))
+ {
+ foreach (scandir($folderPath) as $value)
+ {
+ if ($value != '.' && $value != '..')
+ {
+ $value = $folderPath . "/" . $value;
+
+ if (is_dir($value)) {
+ self::removeDirectories($value);
+ } else if (is_file($value)) {
+ unlink($value);
+ }
+ }
+ }
+
+ return rmdir($folderPath);
+ } else {
+ return false;
+ }
+ }
+
+ public static function copyDirectory($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
+ if ($dest !== "$source/$entry") {
+ self::copyDirectory("$source/$entry", "$dest/$entry");
+ }
+ }
+
+ // Clean up
+ $dir->close();
+
+ return true;
+ }
+
+ public static function getTableAsString(IPF_ORM_Table $table)
+ {
+ $r[] = "<pre>";
+ $r[] = "Component : ".$table->getComponentName();
+ $r[] = "Table : ".$table->getTableName();
+ $r[] = "</pre>";
+
+ return implode("\n",$r)."<br>";
+ }
+
+ public static function getCollectionAsString(IPF_ORM_Collection $collection)
+ {
+ $r[] = "<pre>";
+ $r[] = get_class($collection);
+ $r[] = 'data : ' . IPF_ORM::dump($collection->getData(), false);
+ //$r[] = 'snapshot : ' . IPF_ORM::dump($collection->getSnapshot());
+ $r[] = "</pre>";
+ return implode("\n",$r);
+ }
+
+
+}
+
--- /dev/null
+<?php
+
+class IPF_ORM_Validator extends IPF_ORM_Locator_Injectable
+{
+ private static $validators = array();
+ public static function getValidator($name)
+ {
+ if ( ! isset(self::$validators[$name])) {
+ $class = 'IPF_ORM_Validator_' . ucwords(strtolower($name));
+ if (class_exists($class)) {
+ self::$validators[$name] = new $class;
+ } else if (class_exists($name)) {
+ self::$validators[$name] = new $name;
+ } else {
+ throw new IPF_ORM_Exception("Validator named '$name' not available.");
+ }
+
+ }
+ return self::$validators[$name];
+ }
+
+ public function validateRecord(IPF_ORM_Record $record)
+ {
+ $table = $record->getTable();
+
+ // if record is transient all fields will be validated
+ // if record is persistent only the modified fields will be validated
+ $fields = $record->exists() ? $record->getModified():$record->getData();
+ foreach ($fields as $fieldName => $value) {
+ $table->validateField($fieldName, $value, $record);
+ }
+ }
+
+ public static function validateLength($value, $type, $maximumLength)
+ {
+ if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') {
+ return true;
+ } else if ($type == 'array' || $type == 'object') {
+ $length = strlen(serialize($value));
+ } else {
+ $length = strlen($value);
+ }
+ if ($length > $maximumLength) {
+ return false;
+ }
+ return true;
+ }
+
+ public function hasErrors()
+ {
+ return (count($this->stack) > 0);
+ }
+
+ public static function isValidType($var, $type)
+ {
+ if ($var instanceof IPF_ORM_Expression) {
+ return true;
+ } else if ($var === null) {
+ return true;
+ } else if (is_object($var)) {
+ return $type == 'object';
+ }
+
+ switch ($type) {
+ case 'float':
+ case 'double':
+ case 'decimal':
+ return (string)$var == strval(floatval($var));
+ case 'integer':
+ return (string)$var == strval(intval($var));
+ case 'string':
+ return is_string($var) || is_numeric($var);
+ case 'blob':
+ case 'clob':
+ case 'gzip':
+ return is_string($var);
+ case 'array':
+ return is_array($var);
+ case 'object':
+ return is_object($var);
+ case 'boolean':
+ return is_bool($var) || (is_numeric($var) && ($var == 0 || $var == 1));
+ case 'timestamp':
+ $validator = self::getValidator('timestamp');
+ return $validator->validate($var);
+ case 'time':
+ $validator = self::getValidator('time');
+ return $validator->validate($var);
+ case 'date':
+ $validator = self::getValidator('date');
+ return $validator->validate($var);
+ case 'enum':
+ return is_string($var) || is_int($var);
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Country
+{
+ private static $countries = array(
+ 'ad' => 'Andorra',
+ 'ae' => 'United Arab Emirates',
+ 'af' => 'Afghanistan',
+ 'ag' => 'Antigua and Barbuda',
+ 'ai' => 'Anguilla',
+ 'al' => 'Albania',
+ 'am' => 'Armenia',
+ 'an' => 'Netherlands Antilles',
+ 'ao' => 'Angola',
+ 'aq' => 'Antarctica',
+ 'ar' => 'Argentina',
+ 'as' => 'American Samoa',
+ 'at' => 'Austria',
+ 'au' => 'Australia',
+ 'aw' => 'Aruba',
+ 'az' => 'Azerbaijan',
+ 'ba' => 'Bosnia Hercegovina',
+ 'bb' => 'Barbados',
+ 'bd' => 'Bangladesh',
+ 'be' => 'Belgium',
+ 'bf' => 'Burkina Faso',
+ 'bg' => 'Bulgaria',
+ 'bh' => 'Bahrain',
+ 'bi' => 'Burundi',
+ 'bj' => 'Benin',
+ 'bm' => 'Bermuda',
+ 'bn' => 'Brunei Darussalam',
+ 'bo' => 'Bolivia',
+ 'br' => 'Brazil',
+ 'bs' => 'Bahamas',
+ 'bt' => 'Bhutan',
+ 'bv' => 'Bouvet Island',
+ 'bw' => 'Botswana',
+ 'by' => 'Belarus (Byelorussia)',
+ 'bz' => 'Belize',
+ 'ca' => 'Canada',
+ 'cc' => 'Cocos Islands',
+ 'cd' => 'Congo, The Democratic Republic of the',
+ 'cf' => 'Central African Republic',
+ 'cg' => 'Congo',
+ 'ch' => 'Switzerland',
+ 'ci' => 'Ivory Coast',
+ 'ck' => 'Cook Islands',
+ 'cl' => 'Chile',
+ 'cm' => 'Cameroon',
+ 'cn' => 'China',
+ 'co' => 'Colombia',
+ 'cr' => 'Costa Rica',
+ 'cs' => 'Czechoslovakia',
+ 'cu' => 'Cuba',
+ 'cv' => 'Cape Verde',
+ 'cx' => 'Christmas Island',
+ 'cy' => 'Cyprus',
+ 'cz' => 'Czech Republic',
+ 'de' => 'Germany',
+ 'dj' => 'Djibouti',
+ 'dk' => 'Denmark',
+ 'dm' => 'Dominica',
+ 'do' => 'Dominican Republic',
+ 'dz' => 'Algeria',
+ 'ec' => 'Ecuador',
+ 'ee' => 'Estonia',
+ 'eg' => 'Egypt',
+ 'eh' => 'Western Sahara',
+ 'er' => 'Eritrea',
+ 'es' => 'Spain',
+ 'et' => 'Ethiopia',
+ 'fi' => 'Finland',
+ 'fj' => 'Fiji',
+ 'fk' => 'Falkland Islands',
+ 'fm' => 'Micronesia',
+ 'fo' => 'Faroe Islands',
+ 'fr' => 'France',
+ 'fx' => 'France, Metropolitan FX',
+ 'ga' => 'Gabon',
+ 'gb' => 'United Kingdom (Great Britain)',
+ 'gd' => 'Grenada',
+ 'ge' => 'Georgia',
+ 'gf' => 'French Guiana',
+ 'gh' => 'Ghana',
+ 'gi' => 'Gibraltar',
+ 'gl' => 'Greenland',
+ 'gm' => 'Gambia',
+ 'gn' => 'Guinea',
+ 'gp' => 'Guadeloupe',
+ 'gq' => 'Equatorial Guinea',
+ 'gr' => 'Greece',
+ 'gs' => 'South Georgia and the South Sandwich Islands',
+ 'gt' => 'Guatemala',
+ 'gu' => 'Guam',
+ 'gw' => 'Guinea-bissau',
+ 'gy' => 'Guyana',
+ 'hk' => 'Hong Kong',
+ 'hm' => 'Heard and McDonald Islands',
+ 'hn' => 'Honduras',
+ 'hr' => 'Croatia',
+ 'ht' => 'Haiti',
+ 'hu' => 'Hungary',
+ 'id' => 'Indonesia',
+ 'ie' => 'Ireland',
+ 'il' => 'Israel',
+ 'in' => 'India',
+ 'io' => 'British Indian Ocean Territory',
+ 'iq' => 'Iraq',
+ 'ir' => 'Iran',
+ 'is' => 'Iceland',
+ 'it' => 'Italy',
+ 'jm' => 'Jamaica',
+ 'jo' => 'Jordan',
+ 'jp' => 'Japan',
+ 'ke' => 'Kenya',
+ 'kg' => 'Kyrgyzstan',
+ 'kh' => 'Cambodia',
+ 'ki' => 'Kiribati',
+ 'km' => 'Comoros',
+ 'kn' => 'Saint Kitts and Nevis',
+ 'kp' => 'North Korea',
+ 'kr' => 'South Korea',
+ 'kw' => 'Kuwait',
+ 'ky' => 'Cayman Islands',
+ 'kz' => 'Kazakhstan',
+ 'la' => 'Laos',
+ 'lb' => 'Lebanon',
+ 'lc' => 'Saint Lucia',
+ 'li' => 'Lichtenstein',
+ 'lk' => 'Sri Lanka',
+ 'lr' => 'Liberia',
+ 'ls' => 'Lesotho',
+ 'lt' => 'Lithuania',
+ 'lu' => 'Luxembourg',
+ 'lv' => 'Latvia',
+ 'ly' => 'Libya',
+ 'ma' => 'Morocco',
+ 'mc' => 'Monaco',
+ 'md' => 'Moldova Republic',
+ 'mg' => 'Madagascar',
+ 'mh' => 'Marshall Islands',
+ 'mk' => 'Macedonia, The Former Yugoslav Republic of',
+ 'ml' => 'Mali',
+ 'mm' => 'Myanmar',
+ 'mn' => 'Mongolia',
+ 'mo' => 'Macau',
+ 'mp' => 'Northern Mariana Islands',
+ 'mq' => 'Martinique',
+ 'mr' => 'Mauritania',
+ 'ms' => 'Montserrat',
+ 'mt' => 'Malta',
+ 'mu' => 'Mauritius',
+ 'mv' => 'Maldives',
+ 'mw' => 'Malawi',
+ 'mx' => 'Mexico',
+ 'my' => 'Malaysia',
+ 'mz' => 'Mozambique',
+ 'na' => 'Namibia',
+ 'nc' => 'New Caledonia',
+ 'ne' => 'Niger',
+ 'nf' => 'Norfolk Island',
+ 'ng' => 'Nigeria',
+ 'ni' => 'Nicaragua',
+ 'nl' => 'Netherlands',
+ 'no' => 'Norway',
+ 'np' => 'Nepal',
+ 'nr' => 'Nauru',
+ 'nt' => 'Neutral Zone',
+ 'nu' => 'Niue',
+ 'nz' => 'New Zealand',
+ 'om' => 'Oman',
+ 'pa' => 'Panama',
+ 'pe' => 'Peru',
+ 'pf' => 'French Polynesia',
+ 'pg' => 'Papua New Guinea',
+ 'ph' => 'Philippines',
+ 'pk' => 'Pakistan',
+ 'pl' => 'Poland',
+ 'pm' => 'St. Pierre and Miquelon',
+ 'pn' => 'Pitcairn',
+ 'pr' => 'Puerto Rico',
+ 'pt' => 'Portugal',
+ 'pw' => 'Palau',
+ 'py' => 'Paraguay',
+ 'qa' => 'Qatar',
+ 're' => 'Reunion',
+ 'ro' => 'Romania',
+ 'ru' => 'Russia',
+ 'rw' => 'Rwanda',
+ 'sa' => 'Saudi Arabia',
+ 'sb' => 'Solomon Islands',
+ 'sc' => 'Seychelles',
+ 'sd' => 'Sudan',
+ 'se' => 'Sweden',
+ 'sg' => 'Singapore',
+ 'sh' => 'St. Helena',
+ 'si' => 'Slovenia',
+ 'sj' => 'Svalbard and Jan Mayen Islands',
+ 'sk' => 'Slovakia (Slovak Republic)',
+ 'sl' => 'Sierra Leone',
+ 'sm' => 'San Marino',
+ 'sn' => 'Senegal',
+ 'so' => 'Somalia',
+ 'sr' => 'Suriname',
+ 'st' => 'Sao Tome and Principe',
+ 'sv' => 'El Salvador',
+ 'sy' => 'Syria',
+ 'sz' => 'Swaziland',
+ 'tc' => 'Turks and Caicos Islands',
+ 'td' => 'Chad',
+ 'tf' => 'French Southern Territories',
+ 'tg' => 'Togo',
+ 'th' => 'Thailand',
+ 'tj' => 'Tajikistan',
+ 'tk' => 'Tokelau',
+ 'tm' => 'Turkmenistan',
+ 'tn' => 'Tunisia',
+ 'to' => 'Tonga',
+ 'tp' => 'East Timor',
+ 'tr' => 'Turkey',
+ 'tt' => 'Trinidad, Tobago',
+ 'tv' => 'Tuvalu',
+ 'tw' => 'Taiwan',
+ 'tz' => 'Tanzania',
+ 'ua' => 'Ukraine',
+ 'ug' => 'Uganda',
+ 'uk' => 'United Kingdom',
+ 'um' => 'United States Minor Islands',
+ 'us' => 'United States of America',
+ 'uy' => 'Uruguay',
+ 'uz' => 'Uzbekistan',
+ 'va' => 'Vatican City',
+ 'vc' => 'Saint Vincent, Grenadines',
+ 've' => 'Venezuela',
+ 'vg' => 'Virgin Islands (British)',
+ 'vi' => 'Virgin Islands (USA)',
+ 'vn' => 'Viet Nam',
+ 'vu' => 'Vanuatu',
+ 'wf' => 'Wallis and Futuna Islands',
+ 'ws' => 'Samoa',
+ 'ye' => 'Yemen',
+ 'yt' => 'Mayotte',
+ 'yu' => 'Yugoslavia',
+ 'za' => 'South Africa',
+ 'zm' => 'Zambia',
+ 'zr' => 'Zaire',
+ 'zw' => 'Zimbabwe');
+
+ public static function getCountries()
+ {
+ return self::$countries;
+ }
+
+ public function validate($value)
+ {
+ $value = strtolower($value);
+ return isset(self::$countries[$value]);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Creditcard
+{
+ public function validate($value)
+ {
+ $cardType = "";
+ $card_regexes = array(
+ "/^4\d{12}(\d\d\d){0,1}$/" => 'visa',
+ "/^5[12345]\d{14}$/" => 'mastercard',
+ "/^3[47]\d{13}$/" => 'amex',
+ "/^6011\d{12}$/" => 'discover',
+ "/^30[012345]\d{11}$/" => 'diners',
+ "/^3[68]\d{12}$/" => 'diners',
+ );
+ foreach ($card_regexes as $regex => $type) {
+ if (preg_match($regex, $value)) {
+ $cardType = $type;
+ break;
+ }
+ }
+ if ( ! $cardType) {
+ return false;
+ }
+ /* mod 10 checksum algorithm */
+ $revcode = strrev($value);
+ $checksum = 0;
+ for ($i = 0; $i < strlen($revcode); $i++) {
+ $currentNum = intval($revcode[$i]);
+ if ($i & 1) { /* Odd position */
+ $currentNum *= 2;
+ }
+ /* Split digits and add. */
+ $checksum += $currentNum % 10;
+ if ($currentNum > 9) {
+ $checksum += 1;
+ }
+ }
+ if ($checksum % 10 == 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Date
+{
+ public function validate($value)
+ {
+ if ($value === null) {
+ return true;
+ }
+ $e = explode('-', $value);
+
+ if (count($e) !== 3) {
+ return false;
+ }
+ return checkdate($e[1], $e[2], $e[0]);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Driver
+{
+ protected $_args = array();
+
+ public function __get($arg)
+ {
+ if (isset($this->_args[$arg])) {
+ return $this->_args[$arg];
+ }
+ return null;
+ }
+
+ public function __isset($arg)
+ {
+ return isset($this->_args[$arg]);
+ }
+
+ public function __set($arg, $value)
+ {
+ $this->_args[$arg] = $value;
+
+ return $this;
+ }
+
+ public function getArg($arg)
+ {
+ if ( ! isset($this->_args[$arg])) {
+ throw new IPF_ORM_Exception_Validator('Unknown option ' . $arg);
+ }
+ return $this->_args[$arg];
+ }
+
+ public function setArg($arg, $value)
+ {
+ $this->_args[$arg] = $value;
+
+ return $this;
+ }
+
+ public function getArgs()
+ {
+ return $this->_args;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Email
+{
+ public function validate($value)
+ {
+ if ($value === null)
+ return true;
+ return IPF_Utils::isEmail($value);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_ErrorStack extends IPF_ORM_Access implements Countable, IteratorAggregate
+{
+ protected $_errors = array();
+ protected $_validators = array();
+
+ protected $_className;
+
+ public function __construct($className)
+ {
+ $this->_className = $className;
+ }
+
+ public function add($invalidFieldName, $errorCode = 'general')
+ {
+ // FIXME: In the future the error stack should contain nothing but validator objects
+ if (is_object($errorCode) && strpos(get_class($errorCode), 'IPF_ORM_Validator_') !== false) {
+ $validator = $errorCode;
+ $this->_validators[$invalidFieldName][] = $validator;
+ $className = get_class($errorCode);
+ $errorCode = strtolower(substr($className, strlen('IPF_ORM_Validator_'), strlen($className)));
+ }
+
+ $this->_errors[$invalidFieldName][] = $errorCode;
+ }
+
+ public function remove($fieldName)
+ {
+ if (isset($this->_errors[$fieldName])) {
+ unset($this->_errors[$fieldName]);
+ }
+ }
+
+ public function get($fieldName)
+ {
+ return isset($this->_errors[$fieldName]) ? $this->_errors[$fieldName] : null;
+ }
+
+ public function set($fieldName, $errorCode)
+ {
+ $this->add($fieldName, $errorCode);
+ }
+
+ public function contains($fieldName)
+ {
+ return array_key_exists($fieldName, $this->_errors);
+ }
+
+ public function clear()
+ {
+ $this->_errors = array();
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_errors);
+ }
+
+ public function toArray()
+ {
+ return $this->_errors;
+ }
+
+ public function count()
+ {
+ return count($this->_errors);
+ }
+
+ public function getClassname()
+ {
+ return $this->_className;
+ }
+
+ public function getValidators()
+ {
+ return $this->_validators;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Future
+{
+ public function validate($value)
+ {
+ if ($value === null) {
+ return true;
+ }
+ $e = explode('-', $value);
+
+ if (count($e) !== 3) {
+ return false;
+ }
+
+ if (is_array($this->args) && isset($this->args['timezone'])) {
+ switch (strtolower($this->args['timezone'])) {
+ case 'gmt':
+ $now = gmdate("U") - date("Z");
+ break;
+ default:
+ $now = getdate();
+ break;
+ }
+ } else {
+ $now = getdate();
+ }
+
+ if ($now['year'] > $e[0]) {
+ return false;
+ } else if ($now['year'] == $e[0]) {
+ if ($now['mon'] > $e[1]) {
+ return false;
+ } else if ($now['mon'] == $e[1]) {
+ return $now['mday'] < $e[2];
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Htmlcolor
+{
+ public function validate($value)
+ {
+ if ( ! preg_match("/^#{0,1}[0-9a-fA-F]{6}$/", $value)) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Ip
+{
+ public function validate($value)
+ {
+ return (bool) ip2long(str_replace("\0", '', $value));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Minlength
+{
+ public function validate($value)
+ {
+ if (isset($this->args) && strlen($value) < $this->args) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Nospace extends IPF_ORM_Validator_Driver
+{
+ public function validate($value)
+ {
+ return ($value === null || ! preg_match('/\s/', $value));
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Notblank extends IPF_ORM_Validator_Driver
+{
+ public function validate($value)
+ {
+ return (trim($value) !== '' && $value !== null);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Notnull extends IPF_ORM_Validator_Driver
+{
+ public function validate($value)
+ {
+ return ($value !== null);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Past
+{
+ public function validate($value)
+ {
+ if ($value === null) {
+ return true;
+ }
+ $e = explode('-', $value);
+
+ if (count($e) !== 3) {
+ return false;
+ }
+
+ if (is_array($this->args) && isset($this->args['timezone'])) {
+ switch (strtolower($this->args['timezone'])) {
+ case 'gmt':
+ $now = gmdate("U") - date("Z");
+ break;
+ default:
+ $now = getdate();
+ break;
+ }
+ } else {
+ $now = getdate();
+ }
+
+ if ($now['year'] < $e[0]) {
+ return false;
+ } else if ($now['year'] == $e[0]) {
+ if ($now['mon'] < $e[1]) {
+ return false;
+ } else if ($now['mon'] == $e[1]) {
+ return $now['mday'] > $e[2];
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Range
+{
+ public function validate($value)
+ {
+ if (isset($this->args[0]) && $value < $this->args[0]) {
+ return false;
+ }
+ if (isset($this->args[1]) && $value > $this->args[1]) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Readonly
+{
+ public function validate($value)
+ {
+ $modified = $this->invoker->getModified();
+ return array_key_exists($this->field, $modified) ? false : true;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Regexp
+{
+ public function validate($value)
+ {
+ if ( ! isset($this->args)) {
+ return true;
+ }
+ if (is_array($this->args)) {
+ foreach ($this->args as $regexp) {
+ if ( ! preg_match($regexp, $value)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ if (preg_match($this->args, $value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Time
+{
+ public function validate($value)
+ {
+ if ($value === null) {
+ return true;
+ }
+
+ $e = explode(':', $value);
+
+ if (count($e) !== 3) {
+ return false;
+ }
+
+ if ( ! preg_match('/^ *[0-9]{2}:[0-9]{2}:[0-9]{2} *$/', $value)) {
+ return false;
+ }
+
+ $hr = intval($e[0], 10);
+ $min = intval($e[1], 10);
+ $sec = intval($e[2], 10);
+
+ return $hr >= 0 && $hr <= 23 && $min >= 0 && $min <= 59 && $sec >= 0 && $sec <= 59;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Timestamp
+{
+ public function validate($value)
+ {
+ if ($value === null) {
+ return true;
+ }
+
+ if ( ! preg_match('/^ *[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} *$/', $value)) {
+ return false;
+ }
+
+ list($date, $time) = explode(' ', trim($value));
+
+ $dateValidator = IPF_ORM_Validator::getValidator('date');
+ $timeValidator = IPF_ORM_Validator::getValidator('time');
+
+ if ( ! $dateValidator->validate($date)) {
+ return false;
+ }
+
+ if ( ! $timeValidator->validate($time)) {
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Unique
+{
+ public function validate($value)
+ {
+ $table = $this->invoker->getTable();
+ $pks = $table->getIdentifier();
+
+ if ( is_array($pks) ) {
+ $pks = join(',', $pks);
+ }
+
+ $sql = 'SELECT ' . $pks . ' FROM ' . $table->getTableName() . ' WHERE ' . $this->field . ' = ?';
+
+ $values = array();
+ $values[] = $value;
+
+ // If the record is not new we need to add primary key checks because its ok if the
+ // unique value already exists in the database IF the record in the database is the same
+ // as the one that is validated here.
+ $state = $this->invoker->state();
+ if ( ! ($state == IPF_ORM_Record::STATE_TDIRTY || $state == IPF_ORM_Record::STATE_TCLEAN)) {
+ foreach ((array) $table->getIdentifier() as $pk) {
+ $sql .= " AND {$pk} != ?";
+ $values[] = $this->invoker->$pk;
+ }
+ }
+
+ $stmt = $table->getConnection()->getDbh()->prepare($sql);
+ $stmt->execute($values);
+
+ return ( ! is_array($stmt->fetch()));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Unsigned
+{
+ public function validate($value)
+ {
+ $int = (int) $value;
+
+ if ($int != $value || $int < 0) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_ORM_Validator_Usstate
+{
+ private static $states = array(
+ 'AK' => true,
+ 'AL' => true,
+ 'AR' => true,
+ 'AZ' => true,
+ 'CA' => true,
+ 'CO' => true,
+ 'CT' => true,
+ 'DC' => true,
+ 'DE' => true,
+ 'FL' => true,
+ 'GA' => true,
+ 'HI' => true,
+ 'IA' => true,
+ 'ID' => true,
+ 'IL' => true,
+ 'IN' => true,
+ 'KS' => true,
+ 'KY' => true,
+ 'LA' => true,
+ 'MA' => true,
+ 'MD' => true,
+ 'ME' => true,
+ 'MI' => true,
+ 'MN' => true,
+ 'MO' => true,
+ 'MS' => true,
+ 'MT' => true,
+ 'NC' => true,
+ 'ND' => true,
+ 'NE' => true,
+ 'NH' => true,
+ 'NJ' => true,
+ 'NM' => true,
+ 'NV' => true,
+ 'NY' => true,
+ 'OH' => true,
+ 'OK' => true,
+ 'OR' => true,
+ 'PA' => true,
+ 'PR' => true,
+ 'RI' => true,
+ 'SC' => true,
+ 'SD' => true,
+ 'TN' => true,
+ 'TX' => true,
+ 'UT' => true,
+ 'VA' => true,
+ 'VI' => true,
+ 'VT' => true,
+ 'WA' => true,
+ 'WI' => true,
+ 'WV' => true,
+ 'WY' => true
+ );
+ public function getStates()
+ {
+ return self::$states;
+ }
+
+ public function validate($value)
+ {
+ return isset(self::$states[$value]);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+final class IPF_Project{
+
+ private $apps = array();
+
+ static private $instance = NULL;
+
+ static function getInstance(){
+ if (self::$instance == NULL)
+ self::$instance = new IPF_Project();
+ return self::$instance;
+ }
+
+ private function __construct(){
+ $applist = IPF::get('applications');
+ foreach( $applist as &$appname){
+ if (!IPF_Utils::isValidName($appname))
+ throw new IPF_Exception_Panic("Application name \"$name\" is incorrect");
+ $this->apps[$appname] = null;
+ }
+ }
+
+ private function __clone(){
+ }
+
+ private function appClassName($name){
+ return $name.'_App';
+ }
+
+ public function appList(){
+ return $this->apps;
+ }
+
+ // Lazy Application Loader
+ public function getApp($name){
+ if (!array_key_exists($name,$this->apps))
+ throw new IPF_Exception_Panic("Application \"$name\" not found");
+ if ($this->apps[$name]==null){
+ $className = $this->appClassName($name);
+ $this->apps[$name] = new $className();
+ }
+ return $this->apps[$name];
+ }
+
+ public function checkApps(){
+ foreach( $this->apps as $appname=>&$app)
+ $this->getApp($appname);
+ }
+
+ public function generateModels(){
+ foreach( $this->apps as $appname=>&$app)
+ $this->getApp($appname)->generateModels();
+ }
+
+ public function createTablesFromModels(){
+ foreach( $this->apps as $appname=>&$app)
+ $this->getApp($appname)->createTablesFromModels();
+ }
+
+ public function generateSql(){
+ $sql = '';
+ foreach( $this->apps as $appname=>&$app)
+ $sql .= $this->getApp($appname)->generateSql();
+ $sql .= "\n\n";
+ return $sql;
+ }
+
+ private function loadModels(){
+ foreach( $this->apps as $appname=>&$app){
+ $this->getApp($appname)->loadModels();
+ }
+ }
+
+ private function cli(){
+ $cli = new IPF_Cli();
+ $cli->run();
+ }
+
+ public function run(){
+ $dsn = IPF::get('dsn');
+ if ($dsn=='')
+ throw new IPF_Exception_Panic('Specify dsn in config file');
+ IPF_ORM_Manager::connection($dsn);
+ $this->loadModels();
+ if (php_sapi_name()=='cli'){
+ $this->cli();
+ return;
+ }
+ IPF_ORM_Manager::getInstance()->setAttribute(IPF_ORM::ATTR_VALIDATE, IPF_ORM::VALIDATE_ALL);
+ $this->router = new IPF_Router();
+ $this->router->dispatch(IPF_HTTP_URL::getAction());
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Router
+{
+ public static function dispatch($query='')
+ {
+ $query = preg_replace('#^(/)+#', '/', '/'.$query);
+ $req = new IPF_HTTP_Request($query);
+ $middleware = array();
+ foreach (IPF::get('middlewares', array()) as $mw) {
+ $middleware[] = new $mw();
+ }
+ $skip = false;
+ foreach ($middleware as $mw) {
+ if (method_exists($mw, 'processRequest')) {
+ $response = $mw->processRequest($req);
+ if ($response !== false) {
+ // $response is a response
+ $response->render($req->method != 'HEAD' and !defined('IN_UNIT_TESTS'));
+ $skip = true;
+ break;
+ }
+ }
+ }
+ if ($skip === false) {
+ $response = IPF_Router::match($req);
+ if (!empty($req->response_vary_on)) {
+ $response->headers['Vary'] = $req->response_vary_on;
+ }
+ $middleware = array_reverse($middleware);
+ foreach ($middleware as $mw) {
+ if (method_exists($mw, 'processResponse')) {
+ $response = $mw->processResponse($req, $response);
+ }
+ }
+ //var_dump($response);
+ $response->render($req->method != 'HEAD');
+ }
+ return array($req, $response);
+ }
+
+ public static function match($req)
+ {
+ foreach (IPF::get('urls') as $url) {
+ $prefix = $url['prefix'];
+ foreach ($url['urls'] as $suburl){
+ $regex = $prefix.$suburl['regex'];
+ //print "Regex = $regex<br>";
+ //print "Query = {$req->query}<br>";
+ $match = array();
+ if (preg_match($regex, $req->query, $match)) {
+ try{
+ IPF::loadFunction($suburl['func']);
+ $r = $suburl['func']($req, $match);
+ if (!is_a($r,'IPF_HTTP_Response'))
+ return new IPF_HTTP_Response_ServerError('function '.$suburl['func'].'() must return IPF_HTTP_Response instance');
+ return $r;
+ } catch (IPF_Exception $e) {
+ return new IPF_HTTP_Response_ServerErrorDebug($e);
+ }
+ /*
+ try{
+ IPF::loadFunction($suburl['func']);
+ return $suburl['func']($req, $match);
+ } catch (IPF_HTTP_Error404 $e) {
+ // Need to add a 404 error handler
+ // something like IPF::get('404_handler', 'class::method')
+ } catch (Exception $e) {
+ if (IPF::get('debug', false) == true) {
+ return new IPF_HTTP_Response_ServerErrorDebug($e);
+ } else {
+ return new IPF_HTTP_Response_ServerError($e);
+ }
+ }
+ */
+ }
+ }
+ }
+ return new IPF_HTTP_Response_NotFound();
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Session_App extends IPF_Application{
+ public function __construct(){
+ parent::__construct(array(
+ 'models'=>array('Session')
+ ));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Session_Middleware
+{
+ function processRequest(&$request)
+ {
+ $session = new Session();
+ $user = new User();
+ if (!isset($request->COOKIE[IPF::get('session_cookie_id')])) {
+ $request->user = $user;
+ $request->session = $session;
+ return false;
+ }
+ try {
+ $data = $this->_decodeData($request->COOKIE[IPF::get('session_cookie_id')]);
+ } catch (Exception $e) {
+ $request->user = $user;
+ $request->session = $session;
+ return false;
+ }
+ if (isset($data[$user->session_key])) {
+ $found_user = $user->getTable()->find($data[$user->session_key]);
+ if ($found_user->id) {
+ $request->user = $found_user;
+ if (43200 < IPF_Utils::dateCompare($request->user->last_login)) {
+ $request->user->last_login = gmdate('Y-m-d H:i:s');
+ $request->user->save();
+ }
+ } else
+ $request->user = $user;
+ } else
+ $request->user = $user;
+
+ if (isset($data['IPF_SESSION_KEY'])) {
+ $found_session = $session->getTable()->findOneBySession_key($data['IPF_SESSION_KEY']);
+ if ($found_session)
+ $request->session = $found_session;
+ else
+ $request->session = $session;
+ } else {
+ $request->session = $session;
+ }
+ return false;
+ }
+
+ function processResponse($request, $response)
+ {
+ if ($request->session->touched) {
+ $request->session->save();
+ $data = array();
+ if ($request->user->id > 0) {
+ $data[$request->user->session_key] = $request->user->id;
+ }
+ $data['IPF_SESSION_KEY'] = $request->session->session_key;
+ $response->cookies[IPF::get('session_cookie_id')] = $this->_encodeData($data);
+ }
+ return $response;
+ }
+
+ protected function _encodeData($data)
+ {
+ if ('' == ($key = IPF::get('secret_key'))) {
+ throw new IPF_Exception('Security error: "secret_key" is not set in the configuration file.');
+ }
+ $data = serialize($data);
+ return base64_encode($data).md5(base64_encode($data).$key);
+ }
+
+ protected function _decodeData($encoded_data)
+ {
+ $check = substr($encoded_data, -32);
+ $base64_data = substr($encoded_data, 0, strlen($encoded_data)-32);
+ if (md5($base64_data.IPF::get('secret_key')) != $check) {
+ throw new IPF_Exception('The session data may have been tampered.');
+ }
+ return unserialize(base64_decode($base64_data));
+ }
+
+ /*
+ public static function processContext($signal, &$params)
+ {
+ $params['context'] = array_merge($params['context'],
+ IPF_Session_Middleware_ContextPreProcessor($params['request']));
+ }
+ */
+}
+
+/*
+function IPF_Session_Middleware_ContextPreProcessor($request)
+{
+ return array('user' => $request->user);
+}
+
+Pluf_Signal::connect('Pluf_Template_Context_Request::construct',
+ array('Pluf_Middleware_Session', 'processContext'));
+*/
\ No newline at end of file
--- /dev/null
+---
+Session:
+ columns:
+ session_key:
+ primary: true
+ type: string(40)
+ session_data: string
+ expire_data: timestamp
+
--- /dev/null
+<?php
+
+class Session extends BaseSession
+{
+ public $data = array();
+ public $touched = false;
+
+ function clear(){
+ $this->data = array();
+ $this->touched = true;
+ }
+
+ function setData($key, $value=null){
+ if (is_null($value)) {
+ unset($this->data[$key]);
+ } else {
+ $this->data[$key] = $value;
+ }
+ $this->touched = true;
+ }
+
+ function getData($key=null, $default=''){
+ if (is_null($key))
+ return parent::getData();
+
+ if (isset($this->data[$key])) {
+ return $this->data[$key];
+ } else {
+ return $default;
+ }
+ }
+
+ function getNewSessionKey()
+ {
+ $key = md5(microtime().rand(0, 123456789).rand(0, 123456789).IPF::get('secret_key'));
+ return $key;
+ }
+
+ function preSave($event)
+ {
+ $this->session_data = serialize($this->data);
+ if ($this->session_key == '') {
+ $this->session_key = $this->getNewSessionKey();
+ }
+ $this->expire_data = gmdate('Y-m-d H:i:s', time()+31536000);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This class has been auto-generated by the IPF ORM Framework
+ */
+abstract class BaseSession extends IPF_ORM_Record
+{
+ public function setTableDefinition()
+ {
+ $this->setTableName('session');
+ $this->hasColumn('session_key', 'string', 40, array('primary' => true, 'type' => 'string', 'length' => '40'));
+ $this->hasColumn('session_data', 'string', null, array('type' => 'string'));
+ $this->hasColumn('expire_data', 'timestamp', null, array('type' => 'timestamp'));
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+final class IPF_Shortcuts{
+
+ static function GetObjectOr404($object, $id)
+ {
+ $item = new $object($id);
+ if ((int)$id > 0 && $item->id == $id) {
+ return $item;
+ }
+ throw new IPF_HTTP_Error404();
+ }
+
+ static function RenderToResponse($tplfile, $params, $request=null)
+ {
+ $tmpl = new IPF_Template($tplfile);
+ if (is_null($request)) {
+ $context = new IPF_Template_Context($params);
+ } else {
+ $context = new IPF_Template_Context_Request($request, $params);
+ }
+ return new IPF_HTTP_Response($tmpl->render($context));
+ }
+
+ static function GetFormForModel($model, $data=null, $extra=array(), $label_suffix=null)
+ {
+ $extra['model'] = $model;
+ return new IPF_Form_Model($data, $extra, $label_suffix);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Template
+{
+ public $tpl = '';
+ public $folders = array();
+ public $cache = '';
+ public $compiled_template = '';
+ public $template_content = '';
+ public $context = null;
+
+ function __construct($template, $folders=null, $cache=null)
+ {
+ $this->tpl = $template;
+ if (is_null($folders)) {
+ $this->folders = IPF::get('template_dirs');
+ } else {
+ $this->folders = $folders;
+ }
+ if (is_null($cache)) {
+ $this->cache = IPF::get('tmp');
+ } else {
+ $this->cache = $cache;
+ }
+
+ }
+
+ function render($c=null)
+ {
+ $this->compiled_template = $this->getCompiledTemplateName();
+ if (!file_exists($this->compiled_template) or IPF::get('debug')) {
+ $compiler = new IPF_Template_Compiler($this->tpl, $this->folders);
+ $this->template_content = $compiler->getCompiledTemplate();
+ $this->write();
+ }
+ if (is_null($c)) {
+ $c = new IPF_Template_Context();
+ }
+ $this->context = $c;
+ ob_start();
+ $t = $c;
+ try {
+ include $this->compiled_template;
+ } catch (Exception $e) {
+ ob_clean();
+ throw $e;
+ }
+ $a = ob_get_contents();
+ ob_end_clean();
+ return $a;
+ }
+
+ function getCompiledTemplateName()
+ {
+ $_tmp = var_export($this->folders, true);
+ return $this->cache.'/IPF_Template-'.md5($_tmp.$this->tpl).'.phps';
+ }
+
+ function write()
+ {
+ $fp = @fopen($this->compiled_template, 'a');
+ if ($fp !== false) {
+ flock($fp, LOCK_EX);
+ ftruncate($fp, 0);
+ rewind($fp);
+ fwrite($fp, $this->template_content, strlen($this->template_content));
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ @chmod($this->compiled_template, 0777);
+ return true;
+ } else {
+ throw new IPF_Exception_Template(sprintf(__('Cannot write the compiled template: %s'), $this->compiled_template));
+ }
+ return false;
+ }
+
+}
+
+function IPF_Template_unsafe($string)
+{
+ return new IPF_Template_SafeString($string, true);
+}
+
+function IPF_Template_htmlspecialchars($string)
+{
+ return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8');
+}
+
+function IPF_Template_dateFormat($date, $format='%b %e, %Y')
+{
+ if (substr(PHP_OS,0,3) == 'WIN') {
+ $_win_from = array ('%e', '%T', '%D');
+ $_win_to = array ('%#d', '%H:%M:%S', '%m/%d/%y');
+ $format = str_replace($_win_from, $_win_to, $format);
+ }
+ $date = date('Y-m-d H:i:s', strtotime($date.' GMT'));
+ return strftime($format, strtotime($date));
+}
+
+function IPF_Template_timeFormat($time, $format='Y-m-d H:i:s')
+{
+ return date($format, $time);
+}
+
+function IPF_Template_safeEcho($mixed, $echo=true)
+{
+ if (!is_object($mixed) or 'IPF_Template_SafeString' !== get_class($mixed)) {
+ if ($echo) {
+ echo htmlspecialchars((string) $mixed, ENT_COMPAT, 'UTF-8');
+ } else {
+ return htmlspecialchars((string) $mixed, ENT_COMPAT, 'UTF-8');
+ }
+ } else {
+ if ($echo) {
+ echo $mixed->value;
+ } else {
+ return $mixed->value;
+ }
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Template_Compiler
+{
+ protected $_literals;
+ protected $_vartype = array(T_CHARACTER, T_CONSTANT_ENCAPSED_STRING,
+ T_DNUMBER, T_ENCAPSED_AND_WHITESPACE,
+ T_LNUMBER, T_OBJECT_OPERATOR, T_STRING,
+ T_WHITESPACE, T_ARRAY);
+
+ protected $_assignOp = array(T_AND_EQUAL, T_DIV_EQUAL, T_MINUS_EQUAL,
+ T_MOD_EQUAL, T_MUL_EQUAL, T_OR_EQUAL,
+ T_PLUS_EQUAL, T_PLUS_EQUAL, T_SL_EQUAL,
+ T_SR_EQUAL, T_XOR_EQUAL);
+
+ protected $_op = array(T_BOOLEAN_AND, T_BOOLEAN_OR, T_EMPTY, T_INC,
+ T_ISSET, T_IS_EQUAL, T_IS_GREATER_OR_EQUAL,
+ T_IS_IDENTICAL, T_IS_NOT_EQUAL, T_IS_NOT_IDENTICAL,
+ T_IS_SMALLER_OR_EQUAL, T_LOGICAL_AND, T_LOGICAL_OR,
+ T_LOGICAL_XOR, T_SR, T_SL, T_DOUBLE_ARROW);
+
+ protected $_allowedInVar;
+
+ protected $_allowedInExpr;
+
+ protected $_allowedAssign;
+
+ protected $_modifier = array('upper' => 'strtoupper',
+ 'lower' => 'strtolower',
+ 'escxml' => 'htmlspecialchars',
+ 'escape' => 'IPF_Template_htmlspecialchars',
+ 'strip_tags' => 'strip_tags',
+ 'escurl' => 'rawurlencode',
+ 'capitalize' => 'ucwords',
+ // Not var_export because of recursive issues.
+ 'debug' => 'print_r',
+ 'fulldebug' => 'var_export',
+ 'count' => 'count',
+ 'nl2br' => 'nl2br',
+ 'trim' => 'trim',
+ 'unsafe' => 'IPF_Template_unsafe',
+ 'safe' => 'IPF_Template_unsafe',
+ 'date' => 'IPF_Template_dateFormat',
+ 'time' => 'IPF_Template_timeFormat',
+ );
+
+ public $_usedModifiers = array();
+
+ protected $_allowedTags = array(
+ 'url' => 'IPF_Template_Tag_Url',
+ );
+ protected $_extraTags = array();
+
+ protected $_blockStack = array();
+
+ protected $_transStack = array();
+ protected $_transPlural = false;
+
+ protected $_sourceFile;
+
+ protected $_currentTag;
+
+ public $templateFolders = array();
+
+ public $templateContent = '';
+
+ public $_extendBlocks = array();
+
+ public $_extendedTemplate = '';
+
+ function __construct($template_file, $folders=array(), $load=true)
+ {
+ $allowedtags = IPF::get('template_tags', array());
+ $this->_allowedTags = array_merge($allowedtags, $this->_allowedTags);
+ $modifiers = IPF::get('template_modifiers', array());
+ $this->_modifier = array_merge($modifiers, $this->_modifier);
+
+ foreach ($this->_allowedTags as $name=>$model) {
+ $this->_extraTags[$name] = new $model();
+ }
+ $this->_sourceFile = $template_file;
+ $this->_allowedInVar = array_merge($this->_vartype, $this->_op);
+ $this->_allowedInExpr = array_merge($this->_vartype, $this->_op);
+ $this->_allowedAssign = array_merge($this->_vartype, $this->_assignOp,
+ $this->_op);
+ $this->templateFolders = $folders;
+ if ($load) {
+ $this->loadTemplateFile($template_file);
+ }
+ }
+
+ function compile()
+ {
+ $this->compileBlocks();
+ $tplcontent = $this->templateContent;
+ $tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent);
+ $tplcontent = preg_replace('!<\?php(.*?)\?>!s', '', $tplcontent);
+ preg_match_all('!{literal}(.*?){/literal}!s', $tplcontent, $_match);
+ $this->_literals = $_match[1];
+ $tplcontent = preg_replace("!{literal}(.*?){/literal}!s", '{literal}', $tplcontent);
+ // Core regex to parse the template
+ $result = preg_replace_callback('/{((.).*?)}/s',
+ array($this, '_callback'),
+ $tplcontent);
+ if (count($this->_blockStack)) {
+ trigger_error(sprintf(__('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR);
+ }
+ return $result;
+ }
+
+ function getCompiledTemplate()
+ {
+ $result = $this->compile();
+ if (count($this->_usedModifiers)) {
+ $code = array();
+ foreach ($this->_usedModifiers as $modifier) {
+ $code[] = 'IPF::loadFunction(\''.$modifier.'\'); ';
+ }
+ $result = '<?php '.implode("\n", $code).'?>'.$result;
+ }
+ $result = str_replace(array('?><?php', '<?php ?>', '<?php ?>'), '', $result);
+ $result = str_replace("?>\n", "?>\n\n", $result);
+ return $result;
+ }
+
+ function compileBlocks()
+ {
+ $tplcontent = $this->templateContent;
+ $this->_extendedTemplate = '';
+ // Match extends on the first line of the template
+ if (preg_match("!{extends\s['\"](.*?)['\"]}!", $tplcontent, $_match)) {
+ $this->_extendedTemplate = $_match[1];
+ }
+ // Get the blocks in the current template
+ $cnt = preg_match_all("!{block\s(\S+?)}(.*?){/block}!s", $tplcontent, $_match);
+ // Compile the blocks
+ for ($i=0; $i<$cnt; $i++) {
+ if (!isset($this->_extendBlocks[$_match[1][$i]])
+ or false !== strpos($this->_extendBlocks[$_match[1][$i]], '~~{~~superblock~~}~~')) {
+ $compiler = clone($this);
+ $compiler->templateContent = $_match[2][$i];
+ $_tmp = $compiler->compile();
+ $this->updateModifierStack($compiler);
+ if (!isset($this->_extendBlocks[$_match[1][$i]])) {
+ $this->_extendBlocks[$_match[1][$i]] = $_tmp;
+ } else {
+ $this->_extendBlocks[$_match[1][$i]] = str_replace('~~{~~superblock~~}~~', $_tmp, $this->_extendBlocks[$_match[1][$i]]);
+ }
+ }
+ }
+ if (strlen($this->_extendedTemplate) > 0) {
+ // The template of interest is now the extended template
+ // as we are not in a base template
+ $this->loadTemplateFile($this->_extendedTemplate);
+ $this->_sourceFile = $this->_extendedTemplate;
+ $this->compileBlocks(); //It will recurse to the base template.
+ } else {
+ // Replace the current blocks by a place holder
+ if ($cnt) {
+ $this->templateContent = preg_replace("!{block\s(\S+?)}(.*?){/block}!s", "{block $1}", $tplcontent, -1);
+ }
+ }
+ }
+
+ function loadTemplateFile($file)
+ {
+ // FIXME: Very small security check, could be better.
+ if (strpos($file, '..') !== false) {
+ throw new IPF_Exception(sprintf(__('Template file contains invalid characters: %s'), $file));
+ }
+ foreach ($this->templateFolders as $folder) {
+ if (file_exists($folder.'/'.$file)) {
+ $this->templateContent = file_get_contents($folder.'/'.$file);
+ return;
+ }
+ }
+ // File not found in all the folders.
+ throw new IPF_Exception(sprintf(__('Template file not found: %s'), $file));
+ }
+
+ function _callback($matches)
+ {
+ list(,$tag, $firstcar) = $matches;
+ if (!preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) {
+ trigger_error(sprintf(__('Invalid tag syntax: %s'), $tag), E_USER_ERROR);
+ return '';
+ }
+ $this->_currentTag = $tag;
+ if (in_array($firstcar, array('$', '\'', '"'))) {
+ if ('blocktrans' !== end($this->_blockStack)) {
+ return '<?php IPF_Template_safeEcho('.$this->_parseVariable($tag).'); ?>';
+ } else {
+ $tok = explode('|', $tag);
+ $this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag);
+ return '%%'.substr($tok[0], 1).'%%';
+ }
+ } else {
+ if (!preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) {
+ trigger_error(sprintf(__('Invalid function syntax: %s'), $tag), E_USER_ERROR);
+ return '';
+ }
+ if (count($m) == 4){
+ $m[2] = $m[3];
+ }
+ if (!isset($m[2])) $m[2] = '';
+ if($m[1] == 'ldelim') return '{';
+ if($m[1] == 'rdelim') return '}';
+ if ($m[1] != 'include') {
+ return '<?php '.$this->_parseFunction($m[1], $m[2]).'?>';
+ } else {
+ return $this->_parseFunction($m[1], $m[2]);
+ }
+ }
+ }
+
+ function _parseVariable($expr)
+ {
+ $tok = explode('|', $expr);
+ $res = $this->_parseFinal(array_shift($tok), $this->_allowedInVar);
+ foreach ($tok as $modifier) {
+ if (!preg_match('/^(\w+)(?:\:(.*))?$/', $modifier, $m)) {
+ trigger_error(sprintf(__('Invalid modifier syntax: (%s) %s'), $this->_currentTag, $modifier), E_USER_ERROR);
+ return '';
+ }
+ $targs = array($res);
+ if(isset($m[2])){
+ $res = $this->_modifier[$m[1]].'('.$res.','.$m[2].')';
+ }
+ else if (isset($this->_modifier[$m[1]])) {
+ $res = $this->_modifier[$m[1]].'('.$res.')';
+ } else {
+ trigger_error(sprintf(__('Unknown modifier: (%s) %s'), $this->_currentTag, $m[1]), E_USER_ERROR);
+ return '';
+ }
+ if (!in_array($this->_modifier[$m[1]], $this->_usedModifiers)) {
+ $this->_usedModifiers[] = $this->_modifier[$m[1]];
+ }
+ }
+ return $res;
+ }
+
+ function _parseFunction($name, $args)
+ {
+ switch ($name) {
+ case 'if':
+ $res = 'if ('.$this->_parseFinal($args, $this->_allowedInExpr).'): ';
+ array_push($this->_blockStack, 'if');
+ break;
+ case 'else':
+ if (end($this->_blockStack) != 'if') {
+ trigger_error(sprintf(__('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR);
+ }
+ $res = 'else: ';
+ break;
+ case 'elseif':
+ if (end($this->_blockStack) != 'if') {
+ trigger_error(sprintf(__('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR);
+ }
+ $res = 'elseif('.$this->_parseFinal($args, $this->_allowedInExpr).'):';
+ break;
+ case 'foreach':
+ $res = 'foreach ('.$this->_parseFinal($args, array(T_AS, T_DOUBLE_ARROW, T_STRING, T_OBJECT_OPERATOR), array(';','!')).'): ';
+ array_push($this->_blockStack, 'foreach');
+ break;
+ case 'while':
+ $res = 'while('.$this->_parseFinal($args,$this->_allowedInExpr).'):';
+ array_push($this->_blockStack, 'while');
+ break;
+ case '/foreach':
+ case '/if':
+ case '/while':
+ $short = substr($name,1);
+ if(end($this->_blockStack) != $short){
+ trigger_error(sprintf(__('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR);
+ }
+ array_pop($this->_blockStack);
+ $res = 'end'.$short.'; ';
+ break;
+ case 'assign':
+ $res = $this->_parseFinal($args, $this->_allowedAssign).'; ';
+ break;
+ case 'literal':
+ if(count($this->_literals)){
+ $res = '?>'.array_shift($this->_literals).'<?php ';
+ }else{
+ trigger_error(__('End tag of a block missing: literal'), E_USER_ERROR);
+ }
+ break;
+ case '/literal':
+ trigger_error(__('Start tag of a block missing: literal'), E_USER_ERROR);
+ break;
+ case 'block':
+ $res = '?>'.$this->_extendBlocks[$args].'<?php ';
+ break;
+ case 'superblock':
+ $res = '?>~~{~~superblock~~}~~<?php ';
+ break;
+ case 'trans':
+ $argfct = $this->_parseFinal($args, $this->_allowedAssign);
+ $res = 'echo(__('.$argfct.'));';
+ break;
+ case 'blocktrans':
+ array_push($this->_blockStack, 'blocktrans');
+ $res = '';
+ $this->_transStack = array();
+ if ($args) {
+ $this->_transPlural = true;
+ $_args = $this->_parseFinal($args, $this->_allowedAssign,
+ array(';', '[', ']'), true);
+ $res .= '$_b_t_c='.trim(array_shift($_args)).'; ';
+ }
+ $res .= 'ob_start(); ';
+ break;
+ case '/blocktrans':
+ $short = substr($name,1);
+ if(end($this->_blockStack) != $short){
+ trigger_error(sprintf(__('End tag of a block missing: %s'), end($this->_blockStack)), E_USER_ERROR);
+ }
+ $res = '';
+ if ($this->_transPlural) {
+ $res .= '$_b_t_p=ob_get_contents(); ob_end_clean(); echo(';
+ $res .= 'IPF_Translation::sprintf(_n($_b_t_s, $_b_t_p, $_b_t_c), array(';
+ $_tmp = array();
+ foreach ($this->_transStack as $key=>$_trans) {
+ $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_safeEcho('.$_trans.', false)';
+ }
+ $res .= implode(', ', $_tmp);
+ unset($_trans, $_tmp);
+ $res .= ')));';
+ $this->_transStack = array();
+ } else {
+ $res .= '$_b_t_s=ob_get_contents(); ob_end_clean(); ';
+ if (count($this->_transStack) == 0) {
+ $res .= 'echo(__($_b_t_s)); ';
+ } else {
+ $res .= 'echo(IPF_Translation::sprintf(__($_b_t_s), array(';
+ $_tmp = array();
+ foreach ($this->_transStack as $key=>$_trans) {
+ $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_safeEcho('.$_trans.', false)';
+ }
+ $res .= implode(', ', $_tmp);
+ unset($_trans, $_tmp);
+ $res .= '))); ';
+ $this->_transStack = array();
+ }
+ }
+ $this->_transPlural = false;
+ array_pop($this->_blockStack);
+ break;
+ case 'plural':
+ $res = '$_b_t_s=ob_get_contents(); ob_end_clean(); ob_start(); ';
+ break;
+ case 'include':
+ // XXX fixme: Will need some security check, when online editing.
+ $argfct = preg_replace('!^[\'"](.*)[\'"]$!', '$1', $args);
+ $_comp = new IPF_Template_Compiler($argfct, $this->templateFolders);
+ $res = $_comp->compile();
+ $this->updateModifierStack($_comp);
+ break;
+ default:
+ $_end = false;
+ $oname = $name;
+ if (substr($name, 0, 1) == '/') {
+ $_end = true;
+ $name = substr($name, 1);
+ }
+ // Here we should allow custom blocks.
+
+ // Here we start the template tag calls at the template tag
+ // {tag ...} is not a block, so it must be a function.
+ if (!isset($this->_allowedTags[$name])) {
+ trigger_error(sprintf(__('The function tag "%s" is not allowed.'), $name), E_USER_ERROR);
+ }
+ $argfct = $this->_parseFinal($args, $this->_allowedAssign);
+ // $argfct is a string that can be copy/pasted in the PHP code
+ // but we need the array of args.
+ $res = '';
+ if (isset($this->_extraTags[$name])) {
+ if (false == $_end) {
+ if (method_exists($this->_extraTags[$name], 'start')) {
+ $res .= '$_extra_tag = IPF::factory(\''.$this->_allowedTags[$name].'\', $t); $_extra_tag->start('.$argfct.'); ';
+ }
+ if (method_exists($this->_extraTags[$name], 'genStart')) {
+ $res .= $this->_extraTags[$name]->genStart();
+ }
+ } else {
+ if (method_exists($this->_extraTags[$name], 'end')) {
+ $res .= '$_extra_tag = IPF::factory(\''.$this->_allowedTags[$name].'\', $t); $_extra_tag->end('.$argfct.'); ';
+ }
+ if (method_exists($this->_extraTags[$name], 'genEnd')) {
+ $res .= $this->_extraTags[$name]->genEnd();
+ }
+ }
+ }
+ if ($res == '') {
+ trigger_error(sprintf(__('The function tag "{%s ...}" is not supported.'), $oname), E_USER_ERROR);
+ }
+ }
+ return $res;
+ }
+
+ function _parseFinal($string, $allowed=array(),
+ $exceptchar=array(';'), $getAsArray=false)
+ {
+ $tokens = token_get_all('<?php '.$string.'?>');
+ $result = '';
+ $first = true;
+ $inDot = false;
+ $firstok = array_shift($tokens);
+ $afterAs = false;
+ $f_key = '';
+ $f_val = '';
+ $results = array();
+
+ // il y a un bug, parfois le premier token n'est pas T_OPEN_TAG...
+ if ($firstok == '<' && $tokens[0] == '?' && is_array($tokens[1])
+ && $tokens[1][0] == T_STRING && $tokens[1][1] == 'php') {
+ array_shift($tokens);
+ array_shift($tokens);
+ }
+ foreach ($tokens as $tok) {
+ if (is_array($tok)) {
+ list($type, $str) = $tok;
+ $first = false;
+ if($type == T_CLOSE_TAG){
+ continue;
+ }
+ if ($type == T_AS) {
+ $afterAs = true;
+ }
+ if ($type == T_STRING && $inDot) {
+ $result .= $str;
+ } elseif ($type == T_VARIABLE) {
+ $result .= '$t->_vars[\''.substr($str, 1).'\']';
+ } elseif ($type == T_WHITESPACE || in_array($type, $allowed)) {
+ $result .= $str;
+ } else {
+ trigger_error(sprintf(__('Invalid syntax: (%s) %s.'), $this->_currentTag, $str), E_USER_ERROR);
+ return '';
+ }
+ } else {
+ if (in_array($tok, $exceptchar)) {
+ trigger_error(sprintf(__('Invalid character: (%s) %s.'), $this->_currentTag, $tok), E_USER_ERROR);
+ } elseif ($tok == '.') {
+ $inDot = true;
+ $result .= '->';
+ } elseif ($tok == '~') {
+ $result .= '.';
+ } elseif ($tok =='[') {
+ $result.=$tok;
+ } elseif ($tok ==']') {
+ $result.=$tok;
+ } elseif ($getAsArray && $tok == ',') {
+ $results[]=$result;
+ $result='';
+ } else {
+ $result .= $tok;
+ }
+ $first = false;
+ }
+ }
+ if (!$getAsArray) {
+ return $result;
+ } else {
+ if ($result != '') {
+ $results[] = $result;
+ }
+ return $results;
+ }
+ }
+
+ protected function updateModifierStack($compiler)
+ {
+ foreach ($compiler->_usedModifiers as $_um) {
+ if (!in_array($_um, $this->_usedModifiers)) {
+ $this->_usedModifiers[] = $_um;
+ }
+ }
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Template_Context
+{
+ public $_vars;
+
+ function __construct($vars=array())
+ {
+ $this->_vars = new IPF_Template_ContextVars($vars);
+ }
+
+ function get($var)
+ {
+ if (isset($this->_vars[$var])) {
+ return $this->_vars[$var];
+ }
+ return '';
+ }
+
+ function set($var, $value)
+ {
+ $this->_vars[$var] = $value;
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Template_Context_Request extends IPF_Template_Context
+{
+ function __construct($request, $vars=array())
+ {
+ $vars = array_merge(array('request' => $request), $vars);
+ foreach (IPF::get('template_context_processors', array()) as $proc) {
+ IPF::loadFunction($proc);
+ $vars = array_merge($proc($request), $vars);
+ }
+ $params = array('request' => $request,
+ 'context' => $vars);
+
+ //IPF_Signal::send('IPF_Template_Context_Request::construct',
+ // 'IPF_Template_Context_Request', $params);
+ $this->_vars = new IPF_Template_ContextVars($params['context']);
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Template_ContextVars extends ArrayObject
+{
+ function offsetGet($prop)
+ {
+ if (!$this->offsetExists($prop)) {
+ return '';
+ }
+ return parent::offsetGet($prop);
+ }
+
+ function __get($prop)
+ {
+ if (isset($this->$prop)) {
+ return $this->$prop;
+ } else {
+ return $this->offsetGet($prop);
+ }
+ }
+
+ function __toString()
+ {
+ return var_export($this, true);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Template_SafeString
+{
+ public $value = '';
+
+ function __construct($mixed, $safe=false)
+ {
+ if (is_object($mixed) and 'IPF_Template_SafeString' == get_class($mixed)) {
+ $this->value = $mixed->value;
+ } else {
+ $this->value = ($safe) ? $mixed : htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8');
+ }
+ }
+
+ function __toString()
+ {
+ return $this->value;
+ }
+
+ public static function markSafe($string)
+ {
+ return new IPF_Template_SafeString($string, true);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class IPF_Template_Tag
+{
+ protected $context;
+ function __construct($context=null)
+ {
+ $this->context = $context;
+ }
+}
--- /dev/null
+<?php
+
+IPF::loadFunction('IPF_HTTP_URL_urlForView');
+
+class IPF_Template_Tag_Url extends IPF_Template_Tag
+{
+ function start($view, $params=array(), $by_name=false, $get_params=array())
+ {
+ echo IPF_HTTP_URL_urlForView($view, $params, $by_name, $get_params);
+ }
+}
--- /dev/null
+<?php
+
+class IPF_Utils {
+
+ public static function isValidName( $s, $max_length=50 ){
+ if (!is_string($s))
+ return false;
+ if ( (strlen($s)==0) || (strlen($s)>$max_length) )
+ return false;
+ if ( is_numeric($s[0]))
+ return false;
+ if (!preg_match('/^[a-zA-Z0-9_]+$/', $s ))
+ return false;
+ return true;
+ }
+
+ public static function isEmail($value){
+ $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
+ $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
+ $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
+ $quotedPair = '\\x5c[\\x00-\\x7f]';
+ $domainLiteral = "\\x5b($dtext|$quotedPair)*\\x5d";
+ $quotedString = "\\x22($qtext|$quotedPair)*\\x22";
+ $domain_ref = $atom;
+ $subDomain = "($domain_ref|$domainLiteral)";
+ $word = "($atom|$quotedString)";
+ $domain = "$subDomain(\\x2e$subDomain)+";
+ $localPart = "$word(\\x2e$word)*";
+ $addrSpec = "$localPart\\x40$domain";
+ return (bool) preg_match("!^$addrSpec$!D", $value);
+ }
+
+ static function prettySize($size)
+ {
+ $mb = 1024*1024;
+ if ($size > $mb) {
+ $mysize = sprintf('%01.2f', $size/$mb).' '. __('MB');
+ } elseif ($size >= 1024) {
+ $mysize = sprintf('%01.2f', $size/1024).' '.__('KB');
+ } else {
+ $mysize = sprintf('%01.2f', $size/1024).' '.__('bytes');
+ }
+ return $mysize;
+ }
+
+ static function cleanFileName($name)
+ {
+ $name = mb_strtolower($name, 'UTF-8');
+ return mb_ereg_replace("/\015\012|\015|\012|\s|[^A-Za-z0-9\.\-\_]/", '_', $name);
+ }
+
+ static function isValidUrl($url)
+ {
+ $ip = '(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.'
+ .'(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}';
+ $dom = '([a-z0-9\.\-]+)';
+ return (preg_match('!^(http|https|ftp|gopher)\://('.$ip.'|'.$dom.')!i', $url)) ? true : false;
+ }
+
+ static function humanTitle($s){
+ return ucfirst(str_replace('_',' ',$s));
+ }
+
+ static function randomString($len=35)
+ {
+ $string = '';
+ $chars = '0123456789abcdefghijklmnopqrstuvwxyz'
+ .'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()+=-_}{[]><?/';
+ $lchars = strlen($chars);
+ $i = 0;
+ while ($i<$len) {
+ $string .= substr($chars, mt_rand(0, $lchars-1), 1);
+ $i++;
+ }
+ return $string;
+ }
+
+ static function dateCompare($date1, $date2=null)
+ {
+ if (strlen($date1) == 10){
+ $date1 .= ' 23:59:59';
+ }
+ if (is_null($date2)) {
+ $date2 = time();
+ } else {
+ if (strlen($date2) == 10) {
+ $date2 .= ' 23:59:59';
+ }
+ $date2 = strtotime(str_replace('-', '/', $date2));
+ }
+ $date1 = strtotime(str_replace('-', '/', $date1));
+ return $date2 - $date1;
+ }
+
+ static function appLabelByModel($model){
+ foreach (IPF_Project::getInstance()->appList() as $app){
+ foreach($app->modelList() as $m){
+ if ($model==$m)
+ return strtolower($app->getLabel());
+ }
+ }
+ return '';
+ }
+}
+
--- /dev/null
+<?php
+
+final class IPF_Version{
+ static $name = '0.2 alpha';
+}
\ No newline at end of file