diff --git a/dmsctl.vbs b/dmsctl.vbs index 8f63aaa..8b952d1 100644 --- a/dmsctl.vbs +++ b/dmsctl.vbs @@ -1,25 +1,373 @@ -' This script should only be invoked for Vista and Windows 7 +' +' KnowledgeTree +' +' +' + +' Service Name Consts +Const KTOFFICE = "KTOpenOffice" +Const KTSCHEDULER = "KTScheduler" +Const KTLUCENE = "KTLucene" + +' Service Control Manager Error Code Consts +Const SVC_SUCCESS = 0 ' Success +Const SVC_NOT_SUPPORTED = 1 ' Not Supported +Const SVC_ACCESS_DENIED = 2 ' Access Denied +Const SVC_DEPENDENT_SERVICES_RUNNING = 3 ' Dependent Services Running +Const SVC_INVALID_SERVICE_CONTROL = 4 ' Invalid Service Control +Const SVC_SERVICE_CANNOT_ACCEPT_CONTROL = 5 ' Service Cannot Accept Control +Const SVC_SERVICE_NOT_ACTIVE = 6 ' Service Not Active +Const SVC_SERVICE_REQUEST_TIMEOUT = 7 ' Service Request Timeout +Const SVC_UNKNOWN_FAILURE = 8 ' Unknown Failure +Const SVC_PATH_NOT_FOUND = 9 ' Path Not Found +Const SVC_SERVICE_ALREADY_RUNNING = 10 ' Service Already Running +Const SVC_SERVICE_DATABASE_LOCKED = 11 ' Service Database Locked +Const SVC_SERVICE_DEPENDENCY_DELETED = 12 ' Service Dependency Deleted +Const SVC_SERVICE_DEPENDENCY_FAILURE = 13 ' Service Dependency Failure +Const SVC_SERVICE_DISABLED = 14 ' Service Disabled +Const SVC_SERVICE_LOGON_FAILURE = 15 ' Service Logon Failure +Const SVC_SERVICE_MARKED_FOR_DELETION = 16 ' Service Marked For Deletion +Const SVC_SERVICES_NO_THREAD = 17 ' Service No Thread +Const SVC_STATUS_CIRCULAR_DEPENDENCY = 18 ' Status Circular Dependency +Const SVC_STATUS_DUPLICATE_NAME = 19 ' Status Duplicate Name +Const SVC_INVALID_NAME = 20 ' Status Invalid Name +Const SVC_STATUS_INVALID_PARAMETER = 21 ' Status Invalid Parameter +Const SVC_INVALID_SERVICES_ACCOUNT = 22 ' Status Invalid Service Account +Const SVC_STATUS_SERVICE_EXISTS = 23 ' Status Service Exists +Const SVC_SERVICE_ALREADY_PAUSED = 24 ' Service Already Paused 'Detecting current OS -strComputer = "." +Dim strComputer, currOS, doRunAs + +strComputer = "." currOS = "" doRunAs = false Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery ("Select * from Win32_OperatingSystem") -For Each objOperatingSystem in colOperatingSystems - currOS = objOperatingSystem.Caption & " " & objOperatingSystem.Version - currOS = trim(currOS) +For Each objOperatingSystem in colOperatingSystems + currOS = objOperatingSystem.Caption & " " & objOperatingSystem.Version + currOS = trim(currOS) Next -If left(currOS, 19) = "Microsoft Windows 7" Then - doRunAs = true -End If +Public Function isWindowsVista() + isWindows7 = false + If left(currOS, 19) = "Microsoft Windows Vista" Then + isWindows7 = true + End If +End Function + +Public Function isWindows7() + isWindows7 = false + If left(currOS, 19) = "Microsoft Windows 7" Then + isWindows7 = true + End If +End Function + +Public Function isWindows2008() + isWindows7 = false + If left(currOS, 19) = "Microsoft Windows 2008" Then + isWindows7 = true + End If +End Function +' Will call this further down when the individual services need starting If doRunAs = true Then - Set objShell = CreateObject("Shell.Application") - Set objFolder = objShell.Namespace("C:\Program Files (x86)\Zend\ktdms\knowledgetree") - Set objFolderItem = objFolder.ParseName("dmsctl_install.bat") - objFolderItem.InvokeVerb "runas" -End If \ No newline at end of file + 'runAs "C:\Program Files (x86)\Zend\ktdms\knowledgetree", "dmsctl_install.bat" +End If + +Public Sub runAs(ByVal strFolder, ByVal strFile) + Set objShell = CreateObject("Shell.Application") + Set objFolder = objShell.Namespace(strFolder) + Set objFolderItem = objFolder.ParseName(strFile) + objFolderItem.InvokeVerb "runas" +End Sub + +dim objArgs, errMsg, result, strUsage, isSuccess + +strUsage = "USAGE:" &_ +"dmsctl.bat [servicename]" & vbNewLine &_ +vbNewLine &_ +"help - this screen " & vbNewLine &_ +"start - start the services" & vbNewLine &_ +"stop - stop the services" & vbNewLine &_ +"restart - restart the services" & vbNewLine &_ +"install - install the services" & vbNewLine &_ +"uninstall - uninstall the services" & vbNewLine &_ +vbNewLine &_ +"servicename - optional service name to start/stop only that service." & vbNewLine &_ +" only mysql is supported for individual control at this time." + +Set objArgs = WScript.Arguments +If objArgs.count < 1 Then + Wscript.Echo strUsage +Else + Select Case objArgs.Item(0) + Case "install" + isSuccess = true ' Track if anything went wrong + + ' Installing KTOffice + result = exec("C:\Program Files\Zend\ktdms\knowledgetree\var\bin\officeinstall.bat") + + 'Install Failed + If result = 0 Then + isSuccess = false + logEvent "The KnowledgeTree KTOffice service could not be installed" + End If + + ' Installing KTScheduler + result = exec("C:\Program Files\Zend\ktdms\knowledgetree\var\bin\schedulerinstall.bat") + + 'Install Failed + If result = 0 Then + isSuccess = false + logEvent "The KnowledgeTree KTScheduler service could not be installed" + End If + + ' Installing KTLucene + result = exec("C:\Program Files\Zend\ktdms\knowledgetree\var\bin\luceneinstall.bat") + + 'Install Failed + If result = 0 Then + isSuccess = false + logEvent "The KnowledgeTree KTLucene service could not be installed" + End If + + If (isSuccess) Then + Wscript.Echo "The KnowledgeTree services were successfully installed" + Else + Wscript.Echo "There were errors installing the KnowledgeTree services please see the log for details ('knowledgetree/var/log/dmsctl.log')" + End If + + Case "start" + isSuccess = true + + svcName = KTOFFICE + If (NOT isServiceStarted(svcName)) Then + If (NOT startService(svcName)) Then + isSuccess = false + End If + End If + + svcName = KTSCHEDULER + If (NOT isServiceStarted(svcName)) Then + If (NOT startService(svcName)) Then + isSuccess = false + End If + End If + + svcName = KTLUCENE + If (NOT isServiceStarted(svcName)) Then + If (NOT startService(svcName)) Then + isSuccess = false + End If + End If + + If (isSuccess) Then + Wscript.Echo "The KnowledgeTree services were successfully started" + Else + Wscript.Echo "There were errors starting the KnowledgeTree services please see the log for details ('knowledgetree/var/log/dmsctl.log')" + End If + + Case "stop" + isSuccess = true + + svcName = KTOFFICE + If (isServiceStarted(svcName)) Then + If (NOT stopService(svcName)) Then + isSuccess = false + End If + End If + + svcName = KTSCHEDULER + If (isServiceStarted(svcName)) Then + If (NOT stopService(svcName)) Then + isSuccess = false + End If + End If + + svcName = KTLUCENE + If (isServiceStarted(svcName)) Then + If (NOT stopService(svcName)) Then + isSuccess = false + End If + End If + + If (isSuccess) Then + Wscript.Echo "The KnowledgeTree services were successfully stopped" + Else + Wscript.Echo "There were errors sopping the KnowledgeTree services please see the log for details ('knowledgetree/var/log/dmsctl.log')" + End If + + Case "uninstall" + isSuccess = true ' Track if anything went wrong + + ' Stopping Then Uninstalling + 'svcName = KTOFFICE + 'If (isServiceStarted(svcName)) Then + ' If (NOT stopService(svcName)) Then + ' isSuccess = false + ' End If + 'End If + + ' Uninstalling KTOffice + result = exec("sc delete " & KTOFFICE) + + 'Uninstall Failed + If result = 0 Then + isSuccess = false + logEvent "The KnowledgeTree KTOffice service could not be uninstalled" + End If + + ' Stopping Then Uninstalling + 'svcName = KTSCHEDULER + 'If (isServiceStarted(svcName)) Then + ' If (NOT stopService(svcName)) Then + ' isSuccess = false + ' End If + 'End If + + ' Uninstalling KTScheduler + result = exec("sc delete " & KTSCHEDULER) + + 'Uninstall Failed + If result = 0 Then + isSuccess = false + logEvent "The KnowledgeTree KTScheduler service could not be uninstalled" + End If + + + + ' Stopping Then Uninstalling + 'svcName = KTLUCENE + 'If (isServiceStarted(svcName)) Then + ' If (NOT stopService(svcName)) Then + ' isSuccess = false + ' End If + 'End If + + ' Uninstalling KTLucene + result = exec("sc delete " & KTLUCENE) + + 'Uninstall Failed + If result = 0 Then + isSuccess = false + logEvent "The KnowledgeTree KTLucene service could not be uninstalled" + End If + + If (isSuccess) Then + Wscript.Echo "The KnowledgeTree services were uninstalled" + Else + Wscript.Echo "There were errors uninstalling the KnowledgeTree services please see the log for details ('knowledgetree/var/log/dmsctl.log')" + End If + + Case Else + Wscript.Echo strUsage + End Select + +End If + +' Method to interrogate a service +Public Function isServiceStarted(ByVal svcName) + strComputer = "." + Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") + ' Obtain an instance of the the class + ' using a key property value. + Set objShare = objWMIService.Get("Win32_Service.Name='" & svcName & "'") + + ' no InParameters to define + + ' Execute the method and obtain the return status. + ' The OutParameters object in objOutParams + ' is created by the provider. + Set objOutParams = objWMIService.ExecMethod("Win32_Service.Name='" & svcName & "'", "InterrogateService") + + If (objOutParams.ReturnValue = SVC_SERVICE_NOT_ACTIVE) Then + isServiceStarted = FALSE + Else + isServiceStarted = TRUE + End If + +end Function + +' Method to start a service +Public Function startService(ByVal svcName) + strComputer = "." + Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") + ' Obtain an instance of the the class + ' using a key property value. + Set objShare = objWMIService.Get("Win32_Service.Name='" & svcName &"'") + + ' no InParameters to define + + ' Execute the method and obtain the return status. + ' The OutParameters object in objOutParams + ' is created by the provider. + Set objOutParams = objWMIService.ExecMethod("Win32_Service.Name='" & svcName & "'", "StartService") + + If (objOutParams.ReturnValue = SVC_SUCCESS) Then + startService = TRUE + Else + startService = FALSE + End If + +End Function + +' Method to stop a service +Public Function stopService(ByVal svcName) + strComputer = "." + Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") + ' Obtain an instance of the the class + ' using a key property value. + Set objShare = objWMIService.Get("Win32_Service.Name='" & svcName &"'") + + ' no InParameters to define + + ' Execute the method and obtain the return status. + ' The OutParameters object in objOutParams + ' is created by the provider. + Set objOutParams = objWMIService.ExecMethod("Win32_Service.Name='" & svcName & "'", "StopService") + + If (objOutParams.ReturnValue = SVC_SUCCESS) Then + stopService = TRUE + Else + stopService = FALSE + End If + +End Function + +' Execute a command +Public Function exec(ByVal cmd) + + Dim WshShell, oExec + Set WshShell = CreateObject("WScript.Shell") + + Set oExec = WshShell.Exec(cmd) + + Do While oExec.Status = 0 + WScript.Sleep 100 + Loop + + exec = oExec.Status + +End Function + +Public Sub createCustomEventLog(ByVal strName) + Const NO_VALUE = Empty + + Set WshShell = WScript.CreateObject("WScript.Shell") + WshShell.RegWrite _ + "HKLM\System\CurrentControlSet\Services\EventLog\" & strName & "\", NO_VALUE +End Sub + +' Event logging only works on Server 2003, 2000 and XP +Public Sub logEvent(ByVal strMessage) + + If (NOT isWindowsVista() AND NOT isWindows7 AND NOT isWindows2008) Then + Const EVENT_SUCCESS = 0 + Set objShell = Wscript.CreateObject("Wscript.Shell") + objShell.LogEvent EVENT_SUCCESS, strMessage + End If + +End Sub diff --git a/setup/migrate/steps/migrateInstallation.php b/setup/migrate/steps/migrateInstallation.php index 72ed725..e97c313 100644 --- a/setup/migrate/steps/migrateInstallation.php +++ b/setup/migrate/steps/migrateInstallation.php @@ -160,7 +160,7 @@ class migrateInstallation extends step public function checkVersion() { if($this->foundVersion < $this->supportedVersion) { $this->versionError = true; - $this->error[] = "KT installation needs to be 3.6.1 or higher"; + $this->error[] = "KnowledgeTree installation needs to be 3.6.1 or higher"; return false; } @@ -173,7 +173,7 @@ class migrateInstallation extends step $foundVersion = file_get_contents($verFile); return $foundVersion; } else { - $this->error[] = "KT installation version not found"; + $this->error[] = "KnowledgeTree installation version not found"; } return false; @@ -214,10 +214,10 @@ class migrateInstallation extends step return true; } - $this->error[] = "KT installation configuration file empty"; + $this->error[] = "KnowledgeTree installation configuration file empty"; } } else { - $this->error[] = "KT installation configuration file not found"; + $this->error[] = "KnowledgeTree installation configuration file not found"; } } else { $this->error[] = "Please Enter a Location"; diff --git a/setup/upgrade/step.php b/setup/upgrade/step.php index 691224b..5a36d16 100644 --- a/setup/upgrade/step.php +++ b/setup/upgrade/step.php @@ -467,7 +467,7 @@ class Step $foundVersion = file_get_contents($verFile); return $foundVersion; } else { - $this->error[] = "KT installation version not found"; + $this->error[] = "KnowledgeTree installation version not found"; } return false; diff --git a/setup/upgrade/steps/upgradeDatabase.php b/setup/upgrade/steps/upgradeDatabase.php index 0152148..4ebbbfe 100644 --- a/setup/upgrade/steps/upgradeDatabase.php +++ b/setup/upgrade/steps/upgradeDatabase.php @@ -202,7 +202,7 @@ class upgradeDatabase extends Step $foundVersion = file_get_contents($verFile); return $foundVersion; } else { - $this->error[] = "KT installation version not found"; + $this->error[] = "KnowledgeTree installation version not found"; } return false; diff --git a/setup/upgrade/templates/installation.tpl b/setup/upgrade/templates/installation.tpl index ad1b10a..f7a5542 100644 --- a/setup/upgrade/templates/installation.tpl +++ b/setup/upgrade/templates/installation.tpl @@ -8,7 +8,7 @@ You will not be able to log into KnowledgeTree until your the database upgrade process is completed.

- !!NB!! You are advised to backup the database before attempting the upgrade. !!NB!! + You are advised to backup the database before attempting the upgrade.

If you have already done this, you may skip this step and can continue directly to the upgrade. diff --git a/setup/upgrade/upgradeWizard.php b/setup/upgrade/upgradeWizard.php index f41a73e..1e4c873 100644 --- a/setup/upgrade/upgradeWizard.php +++ b/setup/upgrade/upgradeWizard.php @@ -146,18 +146,6 @@ class UpgradeWizard { } /** - * Create upgrade file - * - * @author KnowledgeTree Team - * @access private - * @param none - * @return void - */ - private function createUpgradeFile() { - touch(SYSTEM_DIR.'var'.DS.'bin'.DS."upgrade.lock"); - } - - /** * Remove upgrade file * * @author KnowledgeTree Team @@ -222,8 +210,6 @@ class UpgradeWizard { */ public function dispatch() { $this->load(); - // is this necessary? - $this->createUpgradeFile(); $response = $this->systemChecks(); if($this->util->installationSpecified()) { // Check if the migrator needs to be accessed $this->util->redirect('../wizard/index.php?step_name=install_type'); diff --git a/setup/upgrade/upgrader.php b/setup/upgrade/upgrader.php index ccabb71..7ceb3f1 100644 --- a/setup/upgrade/upgrader.php +++ b/setup/upgrade/upgrader.php @@ -400,20 +400,6 @@ class Upgrader { for ($i=1; $i< count($steps)+1; $i++) { $this->_upgradeHelper($steps[$i]); } - - $this->_completeUpgrade(); - } - - /** - * Complete upgrade cleanup process - * - * @author KnowledgeTree Team - * @param none - * @access private - * @return void - */ - private function _completeUpgrade() { - touch("upgrade"); } /** diff --git a/setup/wizard/installUtil.php b/setup/wizard/installUtil.php index 9a4f87a..400bd7f 100644 --- a/setup/wizard/installUtil.php +++ b/setup/wizard/installUtil.php @@ -878,7 +878,7 @@ class InstallUtil { return $type; } - return false; + return "community"; } /** diff --git a/setup/wizard/lib/services/windowsLucene.php b/setup/wizard/lib/services/windowsLucene.php index 7d1fdaa..a370651 100644 --- a/setup/wizard/lib/services/windowsLucene.php +++ b/setup/wizard/lib/services/windowsLucene.php @@ -424,7 +424,7 @@ class windowsLucene extends windowsService { $luceneExe = $this->getLuceneExe(); $luceneSource = $this->getLuceneSource(); $luceneDir = $this->getluceneDir(); - $cmd = "\"{$luceneExe}\""." -install \"".$this->getName()."\" -description "."\"".$this->description."\""." \"".$this->getJavaJVM(). "\" -Djava.class.path=\"".$luceneSource."\"". " -start ".$this->getLuceneServer(). " -out \"".$this->getLuceneOut()."\" -err \"".$this->getLuceneError()."\" -current \"".$luceneDir."\" -auto"; + $cmd = "\"{$luceneExe}\""." -install \"".$this->getName()."\" \"".$this->getJavaJVM(). "\" -Djava.class.path=\"".$luceneSource."\"". " -start ".$this->getLuceneServer(). " -out \"".$this->getLuceneOut()."\" -err \"".$this->getLuceneError()."\" -current \"".$luceneDir."\" -auto"; if(DEBUG) { echo "$cmd
"; return false; diff --git a/setup/wizard/steps/database.php b/setup/wizard/steps/database.php index caff7a2..1f7e36e 100644 --- a/setup/wizard/steps/database.php +++ b/setup/wizard/steps/database.php @@ -638,6 +638,9 @@ class database extends Step $this->error['con'] = "Could not populate schema "; } $this->writeBinaries(); + $port = $conf['server']['port']; + $iserverPorts = 'UPDATE config_settings SET value = "'.$port.'" where group_name = "server" and item IN("internal_server_port", "server_port");'; // Update internal server port + $this->util->dbUtilities->query($iserverPorts); // ensure a guid was generated and is stored $this->util->getSystemIdentifier(); $this->reBuildPaths(); @@ -767,10 +770,13 @@ class database extends Step $this->parse_mysql_dump($sqlFile); $dropPluginHelper = "TRUNCATE plugin_helper;"; // Remove plugin helper table $this->util->dbUtilities->query($dropPluginHelper); - $this->reBuildPaths(); - + $conf = $this->util->getDataFromSession('configuration'); + $port = $conf['server']['port']; + $iserverPorts = 'UPDATE config_settings SET value = "'.$port.'" where group_name = "server" and item IN("internal_server_port", "server_port");'; // Update internal server port + $this->util->dbUtilities->query($iserverPorts); $updateExternalBinaries = 'UPDATE config_settings c SET c.value = "default" where c.group_name = "externalBinary";'; // Remove references to old paths $this->util->dbUtilities->query($updateExternalBinaries); + $this->reBuildPaths(); $this->writeBinaries(); // Rebuild some of the binaries $this->util->getSystemIdentifier(); // ensure a guid was generated and is stored diff --git a/setup/wizard/templates/database.tpl b/setup/wizard/templates/database.tpl index 01bcfc0..86fc6bb 100644 --- a/setup/wizard/templates/database.tpl +++ b/setup/wizard/templates/database.tpl @@ -11,7 +11,7 @@
This step configures the connection to the database server and installs the database. The details for an administrative
- user on the database server are required in order to be able to configure and install the installation database. + user on the database server are required in order to be able to configure and install the KnowledgeTree database.
diff --git a/templates/ktcore/principals/about.smarty b/templates/ktcore/principals/about.smarty index 0426788..56e1ca9 100644 --- a/templates/ktcore/principals/about.smarty +++ b/templates/ktcore/principals/about.smarty @@ -5,8 +5,6 @@ {if ($smallVersion !== 'Community Edition')} {i18n}All rights reserved.{/i18n}
{/if} - -{i18n}.{/i18n}
{if ($smallVersion == 'Community Edition')} {i18n}This program is free software and published under the GNU General Public License version 3{/i18n}

@@ -31,143 +29,55 @@
  • Blogs: See what the KnowledgeTree team have to say
  • +

    Thanks to the following contributors for helping us with code contributions and testing...

    -
      -
    • - Fu Bin -
    • -
    • - Blackhold -
    • -
    • - Chris Buechler -
    • -
    • - Guillaume Dufloux -
    • -
    • - David Alfonso Faspar -
    • -
    • - Ruud Feringa -
    • -
    • - Hilário Fernandes -
    • -
    • - William Hawkins -
    • -
    • - Lody Hoekstra -
    • -
    • - Kenny Horan -
    • -
    • - Artur Kiwa -
    • -
    • - Michael Knight -
    • -
    • - Jeongkyu Kim -
    • -
    • - Rogerio Kohler -
    • -
    • - Piotr Krawiecki -
    • -
    • - Ola Larsson -
    • -
    • - Pavel Lastovicka -
    • -
    • - Michel Loiseleur -
    • -
    • - Renat Lumpau -
    • -
    • - Marco Lusini -
    • -
    • - Michael Malone -
    • -
    • - Jochem Meijers -
    • -
    • - John Miles -
    • -
    • - Ismail Mogal -
    • -
    • - David Nalley -
    • -
    • - Marco Napetti -
    • -
    • - Manuela Patrono -
    • -
    • - Antti Poro -
    • -
    • - Nicolas Quienot -
    • -
    • - Ken Rainsforth -
    • -
    • - Antonio Rizzelli -
    • -
    • - Greg Rundlett -
    • -
    • - Leonardo Russo -
    • -
    • - Ricardo Silva -
    • -
    • - Mario Steinhoff -
    • -
    • - Phillip Steinbachs -
    • -
    • - Tahir Tahang -
    • -
    • - Paul Trgina -
    • -
    • - Harry Tsio -
    • -
    • - Bjarte Kalstveit Vebjørnsen -
    • -
    • - Zakariah -
    • -
    • - Jaime Zarate -
    • -
    • - And all the KnowledgeTree staff that "dogfood" KnowledgeTree every day. -
    • - -
    + Fu Bin, + Blackhold, + Chris Buechler, + Guillaume Dufloux, + David Alfonso Faspar, + Ruud Feringa, + Hilário Fernandes, + William Hawkins, + Lody Hoekstra, + Kenny Horan, + Artur Kiwa, + Michael Knight, + Jeongkyu Kim, + Rogerio Kohler, + Piotr Krawiecki , + Ola Larsson, + Pavel Lastovicka, + Michel Loiseleur , + Renat Lumpau, + Marco Lusini, + Michael Malone, + Jochem Meijers, + John Miles, + Ismail Mogal, + David Nalley, + Marco Napetti, + Manuela Patrono, + Antti Poro, + Nicolas Quienot, + Ken Rainsforth, + Antonio Rizzelli, + Greg Rundlett, + Leonardo Russo, + Ricardo Silva, + Mario Steinhoff, + Phillip Steinbachs, + Tahir Tahang, + Paul Trgina, + Harry Tsio, + Bjarte Kalstveit Vebjørnsen, + Zakariah, + Jaime Zarate,

    + And the KnowledgeTree team who "dogfood" KnowledgeTree every day.

    [If you feel you should be here too, please let us know at contributions@knowledgetree.com]

    - +

    This software utilizes third-party software from Pear, PHPMailer, Smarty Template Engine, JSCalendar, Mochikit, Moxiecode Systems, Yahoo Developer Network.