--- /dev/null
+Version 1.0
+
+Copyright (c) 2002-2005 The Horde Project. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. The end-user documentation included with the redistribution, if
+any, must include the following acknowledgment:
+
+ "This product includes software developed by the Horde Project
+ (http://www.horde.org/)."
+
+Alternately, this acknowledgment may appear in the software itself, if
+and wherever such third-party acknowledgments normally appear.
+
+4. The names "Horde", "The Horde Project", and "Mnemo" must not be
+used to endorse or promote products derived from this software without
+prior written permission. For written permission, please contact
+core@horde.org.
+
+5. Products derived from this software may not be called "Horde" or
+"Mnemo", nor may "Horde" or "Mnemo" appear in their name, without
+prior written permission of the Horde Project.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE HORDE PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+This software consists of voluntary contributions made by many
+individuals on behalf of the Horde Project. For more information on
+the Horde Project, please see <http://www.horde.org/>.
--- /dev/null
+What is Skoli?
+==============
+
+:Last update: $Date: $
+:Revision: $Revision: 0.1 $
+:Contact: horde@lists.horde.org
+
+.. contents:: Contents
+.. section-numbering::
+
+Skoli is a simple administrative module for teachers. Several contacts
+(students) will be summarized to a class. To each student one can then
+enter marks, objectives, outcomes and absences. Besides offering a search
+function Skoli also offers an export option in the CSV and TSV formats.
+
+This software is OSI Certified Open Source Software. OSI Certified is a
+certification mark of the `Open Source Initiative`_.
+
+.. _`Open Source Initiative`: http://www.opensource.org/
+
+
+Obtaining Skoli
+---------------
+
+Further information on Skoli and the latest version can be obtained at
+
+ http://www.horde.org/skoli/
+
+
+Documentation
+-------------
+
+The following documentation is available in the Skoli distribution:
+
+:README_: This file
+:LICENSE_: Copyright and license information
+:`docs/CHANGES`_: Changes by release
+:`docs/CREDITS`_: Project developers
+:`docs/INSTALL`_: Installation instructions and notes
+:`docs/TODO`_: Development TODO list
+:`docs/UPGRADING`_: Pointers on upgrading from previous Skoli versions
+
+
+Installation
+------------
+
+Instructions for installing Skoli can be found in the file INSTALL_ in the
+``docs/`` directory of the Skoli distribution.
+
+
+Assistance
+----------
+
+If you encounter problems with Skoli, help is available!
+
+The Horde Frequently Asked Questions List (FAQ), available on the Web at
+
+ http://www.horde.org/faq/
+
+The Horde Project runs a number of mailing lists, for individual applications
+and for issues relating to the project as a whole. Information, archives, and
+subscription information can be found at
+
+ http://www.horde.org/mail/
+
+Lastly, Horde developers, contributors and users also make occasional
+appearances on IRC, on the channel #horde on the freenode Network
+(irc.freenode.net).
+
+
+Licensing
+---------
+
+For licensing and copyright information, please see the file LICENSE_
+in the Skoli distribution.
+
+Thanks,
+
+The Skoli team
+
+
+.. _README: ?f=README.html
+.. _LICENSE: http://www.horde.org/licenses/asl.php
+.. _docs/CHANGES: ?f=CHANGES.html
+.. _docs/CREDITS: ?f=CREDITS.html
+.. _INSTALL:
+.. _docs/INSTALL: ?f=INSTALL.html
+.. _docs/TODO: ?f=TODO.html
+.. _docs/UPGRADING: ?f=UPGRADING.html
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/add.php,v 0.1 $
+ *
+ * Copyright 2001-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ */
+
+@define('SKOLI_BASE', dirname(__FILE__));
+require_once SKOLI_BASE . '/lib/base.php';
+require_once SKOLI_BASE . '/lib/Forms/Entry.php';
+
+/* Redirect to create a new class if we don't have access to any class */
+if (count(Skoli::listClasses(false, PERMS_EDIT)) == 0 && Auth::getAuth()) {
+ $notification->push(_("Please create a new Class first."), 'horde.message');
+ header('Location: ' . Horde::applicationUrl('classes/create.php', true));
+ exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$form = new Skoli_EntryForm($vars);
+
+// Execute if the form is valid.
+if ($form->validate($vars)) {
+ $result = $form->execute();
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push($result, 'horde.error');
+ } else {
+ $notification->push(sprintf(_("The new entry for \"%s\" has been added."), $result), 'horde.success');
+ }
+
+ header('Location: ' . Horde::applicationUrl(Util::addParameter('add.php', 'class', $vars->get('class_id')), true));
+ exit;
+}
+
+$title = $form->getTitle();
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'add.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/classes/create.php,v 0.1 $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('SKOLI_BASE', dirname(dirname(__FILE__)));
+require_once SKOLI_BASE . '/lib/base.php';
+require_once SKOLI_BASE . '/lib/Forms/CreateClass.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+ header('Location: ' . Horde::applicationUrl('list.php', true));
+ exit;
+}
+
+// Exit if we don't have access to addressbooks.
+require_once SKOLI_BASE . '/lib/School.php';
+if (!count(Skoli_School::listAddressBooks())) {
+ $notification->push(_("You don't have access to any valid addressbook."), 'horde.error');
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$form = new Skoli_CreateClassForm($vars);
+
+// Execute if the form is valid.
+if ($form->validate($vars)) {
+ $result = $form->execute();
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push($result, 'horde.error');
+ } else {
+ $notification->push(sprintf(_("The class \"%s\" has been created."), $vars->get('name')), 'horde.success');
+ $GLOBALS['display_classes'][] = $form->shareid;
+ $prefs->setValue('display_classes', serialize($GLOBALS['display_classes']));
+ }
+
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+}
+
+$title = $form->getTitle();
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'create.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/classes/delete.php,v 0.1 $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('SKOLI_BASE', dirname(dirname(__FILE__)));
+require_once SKOLI_BASE . '/lib/base.php';
+require_once SKOLI_BASE . '/lib/Forms/DeleteClass.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+ header('Location: ' . Horde::applicationUrl('list.php', true));
+ exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$class_id = $vars->get('c');
+
+$class = $skoli_shares->getShare($class_id);
+if (is_a($class, 'PEAR_Error')) {
+ $notification->push($class, 'horde.error');
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+} elseif (!$class->hasPermission(Auth::getAuth(), PERMS_DELETE)) {
+ $notification->push(_("You are not allowed to delete this class."), 'horde.error');
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+}
+
+$form = new Skoli_DeleteClassForm($vars, $class);
+
+// Execute if the form is valid (must pass with POST variables only).
+if ($form->validate(new Variables($_POST))) {
+ $result = $form->execute();
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push($result, 'horde.error');
+ } elseif ($result) {
+ $notification->push(sprintf(_("The class \"%s\" has been deleted."), $class->get('name')), 'horde.success');
+ }
+
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+}
+
+$title = $form->getTitle();
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'delete.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/classes/edit.php,v 0.1 $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('SKOLI_BASE', dirname(dirname(__FILE__)));
+require_once SKOLI_BASE . '/lib/base.php';
+require_once SKOLI_BASE . '/lib/Forms/EditClass.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+ header('Location: ' . Horde::applicationUrl('list.php', true));
+ exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$class = $skoli_shares->getShare($vars->get('c'));
+if (is_a($class, 'PEAR_Error')) {
+ $notification->push($class, 'horde.error');
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+} elseif (!$class->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+ $notification->push(_("You are not allowed to change this class."), 'horde.error');
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+}
+$vars->set('school', $class->get('school'));
+if (!$vars->exists('marks')) {
+ $vars->set('marks', $class->get('marks'));
+}
+if (!$vars->exists('address_book')) {
+ $vars->set('address_book', $class->get('address_book'));
+}
+
+$form = new Skoli_EditClassForm($vars, $class);
+
+// Execute if the form is valid.
+if ($form->validate($vars)) {
+ $original_name = $class->get('name');
+ $result = $form->execute();
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push($result, 'horde.error');
+ } else {
+ if ($class->get('name') != $original_name) {
+ $notification->push(sprintf(_("The class \"%s\" has been renamed to \"%s\"."), $original_name, $class->get('name')), 'horde.success');
+ } else {
+ $notification->push(sprintf(_("The class \"%s\" has been saved."), $original_name), 'horde.success');
+ }
+ }
+
+ header('Location: ' . Horde::applicationUrl('classes/', true));
+ exit;
+}
+
+if (!$vars->exists('name')) {
+ $vars->set('name', $class->get('name'));
+ $vars->set('description', $class->get('desc'));
+ $vars->set('category', $class->get('category'));
+ foreach ($form->_schoolproperties as $name) {
+ if ($name != 'marks') {
+ $vars->set($name, $class->get($name));
+ }
+ }
+ $studentslist = current(Skoli::listStudents($vars->get('c')));
+ $studentsvars = array();
+ foreach ($studentslist['_students'] as $student) {
+ $studentsvars[] = $student['__key'];
+ }
+ $vars->set('students', $studentsvars);
+}
+
+$title = $form->getTitle();
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'edit.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/classes/index.php,v 0.1 $
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('SKOLI_BASE', dirname(dirname(__FILE__)));
+require_once SKOLI_BASE . '/lib/base.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+ header('Location: ' . Horde::applicationUrl('list.php', true));
+ exit;
+}
+
+$edit_url_base = Horde::applicationUrl('classes/edit.php');
+$perms_url_base = Horde::url($registry->get('webroot', 'horde') . '/services/shares/edit.php?app=skoli', true);
+$delete_url_base = Horde::applicationUrl('classes/delete.php');
+
+$classes = Skoli::listClasses(true);
+$sorted_classes = array();
+foreach ($classes as $class) {
+ $sorted_classes[$class->getName()] = $class->get('name');
+}
+asort($sorted_classes);
+
+$edit_img = Horde::img('edit.png', _("Edit"), null, $registry->getImageDir('horde'));
+$perms_img = Horde::img('perms.png', _("Change Permissions"), null, $registry->getImageDir('horde'));
+$delete_img = Horde::img('delete.png', _("Delete"), null, $registry->getImageDir('horde'));
+
+Horde::addScriptFile('popup.js', 'horde', true);
+Horde::addScriptFile('tables.js', 'horde', true);
+$title = _("Manage Classes");
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+require SKOLI_TEMPLATES . '/classes/list.php';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?xml version="1.0"?>
+<!-- $Horde: skoli/config/conf.xml,v 0.1 $ -->
+<configuration>
+ <configsection name="storage">
+ <configheader>
+ Storage System Settings
+ </configheader>
+ <configswitch name="driver" desc="What storage driver should we use?">sql
+ <case name="sql" desc="SQL">
+ <configsection name="params">
+ <configsql switchname="driverconfig">
+ </configsql>
+ </configsection>
+ </case>
+ </configswitch>
+ </configsection>
+
+ <configsection name="objects">
+ <configheader>
+ Settings for new Objects
+ </configheader>
+ <configboolean name="allow_marks" desc="Allow users to add marks?">true</configboolean>
+ <configboolean name="allow_objectives" desc="Allow users to add objectives?">true</configboolean>
+ <configboolean name="allow_outcomes" desc="Allow users to add outcomes?">true</configboolean>
+ <configboolean name="allow_absences" desc="Allow users to add absences?">true</configboolean>
+ </configsection>
+
+ <configsection name="addresses">
+ <configheader>
+ Address settings
+ </configheader>
+ <configswitch name="storage" desc="In what address book should we store students?">ask
+ <case name="ask" desc="Ask user">
+ </case>
+ <case name="custom" desc="One address book for all students">
+ <configstring name="address_book" desc="Name of a client address book. If this is a shared address book, use the share id, not the source name.">localsql</configstring>
+ </case>
+ </configswitch>
+ <configstring name="name_field" desc="Field name from the client address book to display as name field.">name</configstring>
+ <configswitch name="contact_list" desc="When a new class is created should we also create a new contact list?">user
+ <case name="user" desc="Per user preferences setting">
+ </case>
+ <case name="none" desc="Don't create contact lists">
+ </case>
+ <case name="auto" desc="Automatically create a new contact list">
+ <configstring name="contact_list_name" desc="Enter a default name for new contact lists.<br />NOTE: You can use %c, %g or %s as substitution for the class, grade respectively semester name.">%c - %g - %s</configstring>
+ </case>
+ </configswitch>
+ </configsection>
+
+ <configsection name="menu">
+ <configheader>
+ Menu settings
+ </configheader>
+ <configboolean name="export" desc="Should we display an Export link in the Horde application menus?">true</configboolean>
+ <configmultienum name="apps" desc="Select any applications that should be linked in Skoli's menu">
+ <values>
+ <configspecial name="list-horde-apps" />
+ </values>
+ </configmultienum>
+ </configsection>
+</configuration>
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/config/prefs.php.dist,v 0.1 $
+ *
+ * See horde/config/prefs.php for documentation on the structure of this file.
+ */
+
+$prefGroups['display'] = array(
+ 'column' => _("General Options"),
+ 'label' => _("Display Options"),
+ 'desc' => _("Change your sorting and display options."),
+ 'members' => array('initial_page', 'class_columns', 'sortby_class', 'sortdir_class', 'student_columns', 'sortby_student', 'sortdir_student', 'entry_details_wrap'),
+);
+
+$prefGroups['contactlists'] = array(
+ 'column' => _("General Options"),
+ 'label' => _("Contact Lists"),
+ 'desc' => _("Change your settings for automatically create contact lists."),
+ 'members' => $GLOBALS['conf']['addresses']['contact_list'] == 'user' ? array('contact_list', 'contact_list_name') : array(),
+);
+
+$prefGroups['marks'] = array(
+ 'column' => _("General Options"),
+ 'label' => _("Marks"),
+ 'desc' => _("Define a format for marks"),
+ 'members' => array('marks_roundby')
+);
+
+// default view
+$_prefs['initial_page'] = array(
+ 'value' => 'list',
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'enum',
+ 'enum' => array('list' => _("List Classes"),
+ 'add' => _("New Entry"),
+ 'search' => _("Search")),
+ 'desc' => _("Select the view to display after login:")
+);
+
+// Load constants from lib/Skoli.php
+require_once dirname(__FILE__) . '/../lib/Skoli.php';
+
+// columns in the class list view
+$_prefs['class_columns'] = array(
+ 'value' => 'a:6:{i:0;s:13:"semesterstart";i:1;s:11:"semesterend";i:2;s:5:"grade";i:3;s:8:"semester";i:4;s:8:"location";i:5;s:8:"category";}',
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'multienum',
+ 'enum' => array(SKOLI_SORT_SEMESTERSTART => _("Semester Start"),
+ SKOLI_SORT_SEMESTEREND => _("Semester End"),
+ SKOLI_SORT_GRADE => _("Grade"),
+ SKOLI_SORT_SEMESTER => _("Semester"),
+ SKOLI_SORT_LOCATION => _("Location"),
+ SKOLI_SORT_CATEGORY => _("Category")),
+ 'desc' => _("Select the columns that should be shown in the class list view:")
+);
+
+// user preferred sorting column for classes
+$_prefs['sortby_class'] = array(
+ 'value' => SKOLI_SORT_SEMESTERSTART,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'enum',
+ 'enum' => array(SKOLI_SORT_SEMESTERSTART => _("Semester Start"),
+ SKOLI_SORT_SEMESTEREND => _("Semester End"),
+ SKOLI_SORT_NAME => _("Name"),
+ SKOLI_SORT_GRADE => _("Grade"),
+ SKOLI_SORT_SEMESTER => _("Semester"),
+ SKOLI_SORT_LOCATION => _("Location"),
+ SKOLI_SORT_CATEGORY => _("Category")),
+ 'desc' => _("Sort classes by:"),
+);
+
+// user preferred sorting direction for classes
+$_prefs['sortdir_class'] = array(
+ 'value' => SKOLI_SORT_ASCEND,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'enum',
+ 'enum' => array(SKOLI_SORT_ASCEND => _("Ascending"),
+ SKOLI_SORT_DESCEND => _("Descending")),
+ 'desc' => _("Sort direction for classes:"),
+);
+
+// columns in the student list view
+$_prefs['student_columns'] = array(
+ 'value' => 'a:3:{i:0;s:9:"lastentry";i:1;s:8:"summarks";i:2;s:11:"sumabsences";}',
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'multienum',
+ 'enum' => array(SKOLI_SORT_LASTENTRY => _("Last Entry"),
+ SKOLI_SORT_SUMMARKS => _("Mark average"),
+ SKOLI_SORT_SUMABSENCES => _("Absences")),
+ 'desc' => _("Select the columns that should be shown in the student list view:")
+);
+
+// user preferred sorting column for students
+$_prefs['sortby_student'] = array(
+ 'value' => SKOLI_SORT_NAME,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'enum',
+ 'enum' => array(SKOLI_SORT_NAME => _("Name"),
+ SKOLI_SORT_LASTENTRY => _("Last Entry"),
+ SKOLI_SORT_SUMMARKS => _("Mark average"),
+ SKOLI_SORT_SUMABSENCES => _("Absences")),
+ 'desc' => _("Sort students by:"),
+);
+
+// user preferred sorting direction for students
+$_prefs['sortdir_student'] = array(
+ 'value' => SKOLI_SORT_ASCEND,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'enum',
+ 'enum' => array(SKOLI_SORT_ASCEND => _("Ascending"),
+ SKOLI_SORT_DESCEND => _("Descending")),
+ 'desc' => _("Sort direction for students:"),
+);
+
+// preference for wrapping the details for an entry.
+$_prefs['entry_details_wrap'] = array(
+ 'value' => 100,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'number',
+ 'desc' => _("How many characters of the entry details in search view should we allow to see?")
+);
+
+// preference for contact lists.
+$_prefs['contact_list'] = array(
+ 'value' => 'ask',
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'enum',
+ 'enum' => array('ask' => _("Ask every time"),
+ 'none' => _("Don't create contact lists"),
+ 'auto' => _("Automatically create a new contact list")),
+ 'desc' => _("When a new class is created should we also create a new contact list?")
+);
+
+// template for new contact lists.
+$_prefs['contact_list_name'] = array(
+ 'value' => "%c - %g - %s",
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'text',
+ 'desc' => _("Enter a default name for new contact lists.<br />NOTE: You can use %c, %g or %s as substitution for the class, grade respectively semester name.")
+);
+
+// preference for rounding marks.
+$_prefs['marks_roundby'] = array(
+ 'value' => 2,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'number',
+ 'desc' => _("How many decimal digits should we round marks to?")
+);
+
+// custom settings for marks
+$_prefs['marks_format_custom'] = array(
+ 'value' => "6, 5.5, 5, 4.5, 4, 3.5, 3, 2.5, 2, 1.5, 1",
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'text',
+ 'desc' => _("Enter some custom marks and separate them by comma (best mark first).<br />NOTE: You also need to choose \"Custom settings\" above.")
+);
+
+/**
+ * Hidden preferences
+ */
+
+// show the class list options panel?
+// a value of 0 = no, 1 = yes
+$_prefs['show_panel'] = array(
+ 'value' => 1,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'checkbox',
+ 'desc' => _("Show class list options panel?")
+);
+
+// show students in the class list view?
+$_prefs['show_students'] = array(
+ 'value' => 1,
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'checkbox',
+ 'desc' => _("Show students in the class list?"),
+);
+
+// store the class lists to diplay
+$_prefs['display_classes'] = array(
+ 'value' => 'a:0:{}',
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'implicit'
+);
+
+// store the last object format when adding a new entry
+$_prefs['default_objects_format'] = array(
+ 'value' => 'mark',
+ 'locked' => false,
+ 'shared' => false,
+ 'type' => 'implicit'
+);
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/config/schools.php.dist,v 0.1 $
+ *
+ * This file is where you specify default informations needed to create a
+ * new class. It contains one EXAMPLE. Please remove or comment out that
+ * example if YOU DON'T NEED IT. There are a number of properties that you
+ * can set for each school, including:
+ *
+ * title: The name of the school template.
+ *
+ * grade: List of grades for this school.
+ *
+ * semester: List of semesters. Specify each semester with:
+ *
+ * name: The semester name.
+ *
+ * start: Semester startdate. Currently supported formats are:
+ * timestamp -- Timestamp
+ * ISO week -- ISO week date format: e.g. 'W33-2' is Tuesday
+ * of week 33.
+ * strtotime -- A string who can be parsed by 'strtotime'. See
+ * http://www.php.net/strtotime for more informations.
+ *
+ * end: Semester enddate (same format as 'start').
+ *
+ * location: List of locations, e.g. schoolhouses.
+ *
+ * marks: The format to use for marks. Currently supported formats are:
+ * numbers -- In numbers: e.g. 4.4
+ * percent -- In percent: e.g. 74%
+ * custom -- List with custom marks separated by comma (best mark first):
+ * e.g. 6, 5.5, 5, 4.5, 4, 3.5, 3, 2.5, 2, 1.5, 1
+ *
+ * subjects: List of subjects.
+ *
+ * objectives: Each subject may have a list of objectives.
+ *
+ * objectives: List of objectives without a specific subject.
+ */
+
+/**
+ * If a property is not defined then the user will be able to enter the
+ * missing informations as free text. E.g. the following school template
+ * will accept any user input:
+ */
+$cfgSchools['custom'] = array(
+ 'title' => _("Custom school")
+);
+
+/**
+ * The following school may be used for primary schools in Bern, Switzerland.
+ */
+$cfgSchools['prim_be'] = array(
+ 'title' => _("Sample school"),
+ 'grade' => array(
+ _("1. class"),
+ _("2. class"),
+ _("3. class"),
+ _("4. class"),
+ _("5. class"),
+ _("6. class")
+ ),
+ 'semester' => array(
+ array(
+ 'name' => _("1. term"),
+ 'start' => 'W33-1',
+ 'end' => 'W05-5'
+ ),
+ array(
+ 'name' => _("2. term"),
+ 'start' => 'W07-1',
+ 'end' => 'W27-5'
+ )
+ ),
+ 'location' => array(
+ _("Schoolhouse 1"),
+ _("Schoolhouse 2")
+ ),
+ 'marks' => '6, 5.5, 5, 4.5, 4, 3.5, 3, 2.5, 2, 1.5, 1',
+ 'subjects' => array(
+ _("German") => array(
+ _("Hearing and Talking"),
+ _("Reading"),
+ _("Writing"),
+ ),
+ _("Mathematics") => array(
+ _("Imagination"),
+ _("Skills"),
+ _("Appliance"),
+ _("Problem solving behavior"),
+ ),
+ _("Nature-Human-Environment"),
+ _("Music"),
+ _("Sport"),
+ _("Construct"),
+ _("French") => array(
+ _("Hearing"),
+ _("Talking"),
+ _("Reading"),
+ _("Writing"),
+ ),
+ _("English") => array(
+ _("Hearing"),
+ _("Talking"),
+ _("Reading"),
+ _("Writing"),
+ ),
+ ),
+ 'objectives' => array(
+ _("Motivation to learn and dedication"),
+ _("Concentration, attention, perseverance"),
+ _("Exercise processing"),
+ _("Teamwork and autonomy"),
+ ),
+);
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/data.php,v 0.1 $
+ *
+ * Copyright 2001-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ */
+
+require_once dirname(__FILE__) . '/lib/base.php';
+require_once 'Horde/Data.php';
+
+if (!$conf['menu']['export']) {
+ header('Location: ' . Horde::applicationUrl('list.php', true));
+ exit;
+}
+
+$classes = Skoli::listClasses();
+
+/* If there are no valid classes, abort. */
+if (count($classes) == 0) {
+ $notification->push(_("No classes are currently available. Export is disabled."), 'horde.error');
+ require SKOLI_TEMPLATES . '/common-header.inc';
+ require SKOLI_TEMPLATES . '/menu.inc';
+ require $registry->get('templates', 'horde') . '/common-footer.inc';
+ exit;
+}
+
+$class_options = array();
+foreach ($classes as $key=>$class) {
+ $class_options[] = '<option value="' . htmlspecialchars($key) . '"' . (Util::getFormData('class') == $key ? ' selected="selected"' : '') . '>' .
+ htmlspecialchars($class->get('name')) . "</option>\n";
+}
+
+$wholeclass_option = '<option value="all">' .
+ htmlspecialchars(_("Whole class")) . "</option>\n";
+$student_options = array();
+$student_options[] = $wholeclass_option;
+if (Util::getFormData('class') != '') {
+ $class = Util::getFormData('class');
+} else {
+ reset($classes);
+ $class = key($classes);
+}
+$export_class = current(Skoli::listStudents($class, SKOLI_SORT_NAME, SKOLI_SORT_ASCEND));
+foreach ($export_class['_students'] as $address) {
+ $student_options[] = '<option value="' . htmlspecialchars($address['student_id']) . '">' .
+ htmlspecialchars($address[$conf['addresses']['name_field']]) . "</option>\n";
+}
+
+$actionID = Util::getFormData('actionID');
+
+/* Loop through the action handlers. */
+switch ($actionID) {
+case 'export':
+ $data = array();
+ $driver = &Skoli_Driver::singleton($class);
+ if (Util::getFormData('student') == 'all') {
+ /* Export whole class. */
+ $subjects = $driver->getSubjects('mark');
+ foreach ($export_class['_students'] as $student) {
+ $row = array();
+ $row[_("Class")] = $export_class['name'];
+ $row[_("Firstname")] = $student['firstname'];
+ $row[_("Lastname")] = $student['lastname'];
+
+ /* Absences */
+ $absences = Skoli::sumAbsences($class, $student['student_id']);
+ $row[_("Excused absences")] = $absences[0];
+ $row[_("Absences without valid excuse")] = $absences[1];
+
+ /* Marks */
+ foreach ($subjects as $subject) {
+ $row[$subject] = Skoli::sumMarks($class, $student['student_id'], $subject);
+ }
+
+ /* Outcomes */
+ $outcomes = Skoli::sumOutcomes($class, $student['student_id']);
+ $row[_("Completed outcomes")] = $outcomes[0];
+ $row[_("Open outcomes")] = $outcomes[1];
+
+ $data[] = $row;
+ }
+ /* Make sure that only columns with data are exportet. */
+ if (count($data)) {
+ foreach ($data[0] as $key=>$value) {
+ $emptycolumn = true;
+ foreach ($data as $row) {
+ if ($row[$key] !== '') {
+ $emptycolumn = false;
+ break;
+ }
+ }
+ if ($emptycolumn) {
+ foreach ($data as $rowkey=>$row) {
+ unset($data[$rowkey][$key]);
+ }
+ }
+ }
+ }
+ } else {
+ /* Export all entries for the selected student. */
+ $data[] = array(_("Marks"));
+ $subjects = $driver->getSubjects('mark');
+ foreach ($subjects as $subject) {
+ $params = array(array('name' => 'subject', 'value' => $subject, 'strict' => 1));
+ $marks = Skoli::listEntries($class, Util::getFormData('student'), 'mark', $params, SKOLI_SORT_DATE, SKOLI_SORT_DESCEND);
+ foreach ($marks as $mark) {
+ $data[] = array($subject, $mark['date'], $mark['title'], Skoli::convertNumber($mark['mark']), Skoli::convertNumber($mark['weight']));
+ }
+ }
+
+ $data[] = array(_("Objectives"));
+ $subjects = $driver->getSubjects('objective');
+ foreach ($subjects as $subject) {
+ $params = array(array('name' => 'subject', 'value' => $subject, 'strict' => 1));
+ $objectives = Skoli::listEntries($class, Util::getFormData('student'), 'objective', $params, SKOLI_SORT_DATE, SKOLI_SORT_DESCEND);
+ foreach ($objectives as $objective) {
+ $data[] = array($subject, $objective['date'], $objective['category'], $objective['objective']);
+ }
+ }
+
+ $data[] = array(_("Outcomes"));
+ $outcomes = Skoli::listEntries($class, Util::getFormData('student'), 'outcome', null, SKOLI_SORT_DATE, SKOLI_SORT_DESCEND);
+ foreach ($outcomes as $outcome) {
+ $completed = isset($outcome['completed']) && $outcome['completed'] != '' ? _("Completed") : _("Open");
+ $comment = isset($outcome['comment']) ? $outcome['comment'] : '';
+ $data[] = array($outcome['date'], $outcome['outcome'], $completed, $comment);
+ }
+
+ $data[] = array(_("Absences"));
+ $absences = Skoli::listEntries($class, Util::getFormData('student'), 'absence', null, SKOLI_SORT_DATE, SKOLI_SORT_DESCEND);
+ foreach ($absences as $absence) {
+ $excused = isset($absence['excused']) && $absence['excused'] != '' ? _("Excused") : _("Not excused");
+ $comment = isset($absence['comment']) ? $absence['comment'] : '';
+ $data[] = array($absence['date'], Skoli::convertNumber($absence['absence']), $excused, $comment);
+ }
+
+ /* Make sure that all rows have the same number of columns. */
+ $maxcols = 0;
+ for ($i=0; $i < count($data); $i++) {
+ if (count($data[$i]) > $maxcols) {
+ $maxcols = count($data[$i]);
+ }
+ }
+ for ($i=0; $i < count($data); $i++) {
+ for ($irow=0; $irow < $maxcols; $irow++) {
+ if (!isset($data[$i][$irow])) {
+ $data[$i][$irow] = '';
+ }
+ }
+ }
+ }
+ if (!count($data)) {
+ $notification->push(_("There were no entries to export."), 'horde.message');
+ break;
+ }
+
+ switch (Util::getFormData('exportID')) {
+ case EXPORT_CSV:
+ $csv = &Horde_Data::singleton('csv');
+ $csv->exportFile(_("class.csv"), $data, (Util::getFormData('student') == 'all'));
+ exit;
+
+ case EXPORT_TSV:
+ $tsv = &Horde_Data::singleton('tsv');
+ $tsv->exportFile(_("class.tsv"), $data, (Util::getFormData('student') == 'all'));
+ exit;
+
+ }
+ break;
+}
+
+$title = _("Export Classes");
+
+Horde::addScriptFile('prototype.js', 'horde', true);
+Horde::addScriptFile('effects.js', 'horde', true);
+Horde::addScriptFile('redbox.js', 'horde', true);
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+require SKOLI_TEMPLATES . '/data/export.inc';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+----
+v0.1
+----
+
+[xyz] Initial Release
--- /dev/null
+===========================
+ Skoli Development Team
+===========================
+
+
+Core Developers
+===============
+
+- Martin Blumenthal <tinu@humbapa.ch>
+
+
+Localization
+============
+
+===================== ======================================================
+German Martin Blumenthal <tinu@humbapa.ch>
+===================== ======================================================
--- /dev/null
+======================
+ Installing Skoli 0.1
+======================
+
+:Last update: $Date: $
+:Revision: $Revision: 0.1 $
+:Contact: horde@lists.horde.org
+
+.. contents:: Contents
+.. section-numbering::
+
+This document contains instructions for installing the Skoli administrative
+application for teachers on your system.
+
+For information on the capabilities and features of Skoli, see the file
+README_ in the top-level directory of the Skoli distribution.
+
+
+Obtaining Skoli
+==================
+
+Skoli can be obtained from the Horde website and FTP server, at
+
+ http://www.horde.org/skoli/
+
+ ftp://ftp.horde.org/pub/skoli/
+
+Or use the mirror closest to you:
+
+ http://www.horde.org/mirrors.php
+
+Bleeding-edge development versions of Skoli are available via CVS; see the
+file `docs/HACKING`_ in the Horde distribution, or the website
+http://www.horde.org/source/, for information on accessing the Horde CVS
+repository.
+
+
+Prerequisites
+=============
+
+To function properly, Skoli **requires** the following:
+
+1. A working Horde installation.
+
+ Skoli runs within the `Horde Application Framework`_, a set of common
+ tools for Web applications written in PHP. You must install Horde before
+ installing Skoli.
+
+ .. Important:: Skoli 0.1 requires version 3.0+ of the Horde Framework -
+ earlier versions of Horde will **not** work.
+
+ .. _`Horde Application Framework`: http://www.horde.org/horde/
+
+ The Horde Framework can be obtained from the Horde website and FTP server,
+ at
+
+ http://www.horde.org/horde/
+
+ ftp://ftp.horde.org/pub/horde/
+
+ Many of Skoli's prerequisites are also Horde prerequisites.
+
+ .. Important:: Be sure to have completed all of the steps in the
+ `horde/docs/INSTALL`_ file for the Horde Framework before
+ installing Skoli.
+
+2. SQL support in PHP.
+
+ Skoli stores its data in an SQL database. Build PHP with whichever SQL
+ driver you require; see the Horde INSTALL_ file for details.
+
+3. Turba, the Horde contacts manager.
+
+ Turba is the Horde contact management application, designed to be
+ integrated with other Horde applications to provide a unified interface to
+ contact management throughout the Horde suite.
+
+ Turba is available from:
+
+ http://www.horde.org/turba/
+
+ ftp://ftp.horde.org/pub/turba/
+
+ Turba provides a local address book and an LDAP directory search function
+ to IMP.
+
+ You must use the 2.x branch of Turba with Skoli 0.1.
+
+
+Installing Skoli
+================
+
+Skoli is written in PHP, and must be installed in a web-accessible
+directory. The precise location of this directory will differ from system to
+system. Conventionally, Skoli is installed directly underneath Horde in the
+web server's document tree.
+
+Since Skoli is written in PHP, there is no compilation necessary; simply
+expand the distribution where you want it to reside and rename the root
+directory of the distribution to whatever you wish to appear in the URL. For
+example, with the Apache web server's default document root of
+``/usr/local/apache/htdocs``, you would type::
+
+ cd /usr/local/apache/htdocs/horde
+ tar zxvf /path/to/skoli-x.y.z.tar.gz
+ mv skoli-x.y.z skoli
+
+and would then find Skoli at the URL::
+
+ http://your-server/horde/skoli/
+
+
+Configuring Skoli
+====================
+
+1. Configuring Horde for Skoli
+
+ a. Register the application
+
+ In ``horde/config/registry.php``, find the ``applications['skoli']``
+ stanza. The default settings here should be okay, but you can change
+ them if desired. If you have changed the location of Skoli relative
+ to Horde, either in the URL, in the filesystem or both, you must update
+ the ``fileroot`` and ``webroot`` settings to their correct values.
+
+ If Skoli is not yet present in ``horde/config/registry.php`` you can
+ use something like:
+
+ $this->applications['skoli'] = array(
+ 'fileroot' => dirname(__FILE__) . '/../skoli',
+ 'webroot' => $this->applications['horde']['webroot'] . '/skoli',
+ 'name' => _("School"),
+ 'status' => 'active',
+ 'menu_parent' => 'office'
+ );
+
+ $this->applications['skoli-menu'] = array(
+ 'status' => 'block',
+ 'app' => 'skoli',
+ 'blockname' => 'tree_menu',
+ 'menu_parent' => 'skoli',
+ );
+
+2. Creating the database tables
+
+ The specific steps to create Skoli's database tables depend on which
+ database you've chosen to use.
+
+ First, look in ``scripts/sql/`` to see if a script already exists for your
+ database type. If so, you should be able to simply execute that script as
+ superuser in your database. (Note that executing the script as the "horde"
+ user will probably fail when granting privileges.)
+
+ If such a script does not exist, you'll need to build your own, using the
+ file ``skoli.sql`` as a starting point. If you need assistance in
+ creating database tables, you may wish to let us know on the Skoli
+ mailing list.
+
+3. Configuring Skoli
+
+ To configure Skoli, change to the ``config/`` directory of the installed
+ distribution, and make copies of all of the configuration ``dist`` files
+ without the ``dist`` suffix::
+
+ cd config/
+ for foo in *.dist; do cp $foo `basename $foo .dist`; done
+
+ Or on Windows::
+
+ copy *.dist *.
+
+ Documentation on the format and purpose of those files can be found in each
+ file. You may edit these files if you wish to customize Skoli's
+ appearance and behavior. With one exception (``foo.php``) the defaults will
+ be correct for most sites.
+
+ You must login to Horde as a Horde Administrator to finish the
+ configuration of Skoli. Use the Horde ``Administration`` menu item to
+ get to the administration page, and then click on the ``Configuration``
+ icon to get the configuration page. Select ``Skoli Name`` from the
+ selection list of applications. Fill in or change any configuration values
+ as needed. When done click on ``Generate Skoli Name Configuration`` to
+ generate the ``conf.php`` file. If your web server doesn't have write
+ permissions to the Skoli configuration directory or file, it will not be
+ able to write the file. In this case, go back to ``Configuration`` and
+ choose one of the other methods to create the configuration file
+ ``skoli/config/conf.php``.
+
+ Note for international users: Skoli uses GNU gettext to provide local
+ translations of text displayed by applications; the translations are found
+ in the ``po/`` directory. If a translation is not yet available for your
+ locale (and you wish to create one), see the ``horde/po/README`` file, or
+ if you're having trouble using a provided translation, please see the
+ `horde/docs/TRANSLATIONS`_ file for instructions.
+
+4. School templates
+
+ To customize your school edit the file ``skoli/config/school.php``. It
+ contains some examples you can start from.
+
+5. Testing Skoli
+
+ Use Skoli to create a class and add some entries. Test at least the
+ following:
+
+ - Creating a new Class
+ - Adding a new entry for each desired type
+ - Modifying an entry
+ - Deleting an entry
+
+
+Obtaining Support
+=================
+
+If you encounter problems with Skoli, help is available!
+
+The Horde Frequently Asked Questions List (FAQ), available on the Web at
+
+ http://www.horde.org/faq/
+
+The Horde Project runs a number of mailing lists, for individual applications
+and for issues relating to the project as a whole. Information, archives, and
+subscription information can be found at
+
+ http://www.horde.org/mail/
+
+Lastly, Horde developers, contributors and users may also be found on IRC,
+on the channel #horde on the Freenode Network (irc.freenode.net).
+
+Please keep in mind that Skoli is free software written by volunteers.
+For information on reasonable support expectations, please read
+
+ http://www.horde.org/support.php
+
+Thanks for using Skoli!
+
+The Skoli team
+
+
+.. _README: ?f=README.html
+.. _`horde/docs/HACKING`: ../../horde/docs/?f=HACKING.html
+.. _`horde/docs/INSTALL`: ../../horde/docs/?f=INSTALL.html
+.. _`horde/docs/TRANSLATIONS`: ../../horde/docs/?f=TRANSLATIONS.html
--- /dev/null
+<?php
+/**
+ * Release focus. Possible values:
+ * 0 - N/A
+ * 1 - Initial freshmeat announcement
+ * 2 - Documentation
+ * 3 - Code cleanup
+ * 4 - Minor feature enhancements
+ * 5 - Major feature enhancements
+ * 6 - Minor bugfixes
+ * 7 - Major bugfixes
+ * 8 - Minor security fixes
+ * 9 - Major security fixes
+ */
+$this->notes['fm']['focus'] = 4;
+
+/* Mailing list release notes. */
+$this->notes['ml']['changes'] = <<<ML
+The Horde Team is pleased to announce the [first release candidate|final
+release] of the Skoli Foo Bar Application version H3 (x.x).
+
+Skoli is a simple administrative module for teachers. Several contacts
+(students) will be summarized to a class. To each student one can then
+enter marks, objectives, outcomes and absences. Besides offering a search
+function Skoli also offers an export option in the CSV and TSV formats.
+
+[For alpha/beta releases:
+This is a preview version that should not be used on production systems. This
+version is considered feature complete but there might still be a few bugs.
+You should not use this preview version over existing production data.
+
+We encourage widespread testing and feedback via the mailing lists or our bug
+tracking system. Updated translations are very welcome, though some strings
+might still change before the final release.]
+
+[For release candidates:
+Barring any problems, this code will be released as Skoli H3 (x.x).
+Testing is requested and comments are encouraged.
+Updated translations would also be great.]
+
+The major changes compared to the Skoli version H3 (x.x) are:
+[or: Changes in this release:]
+ * ...
+ML;
+
+/* Freshmeat release notes, not more than 600 characters. */
+$this->notes['fm']['changes'] = <<<FM
+FM;
+
+$this->notes['name'] = 'Skoli';
+$this->notes['fm']['project'] = 'skoli';
+$this->notes['fm']['branch'] = 'Default';
--- /dev/null
+=============================
+ Skoli Development TODO List
+=============================
+
+:Last update: $Date: $
+:Revision: $Revision: 0.1 $
+:Contact: horde@lists.horde.org
+
+- When adding new entries allow users to add different values for more than one student (e.g. adding marks for the whole class)
+
+- Tune up the search form with type dependent options (e.g. drop-down with used subjects).
+
+- Implement an easy form to create timetables in e.g. Kronolith.
+
+- Allow users to search for an address to add to classes (e.g. like the compose addressbook window in imp).
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/entry.php,v 0.1 $
+ *
+ * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ */
+
+@define('SKOLI_BASE', dirname(__FILE__));
+require_once SKOLI_BASE . '/lib/base.php';
+require_once 'Horde/Variables.php';
+require_once 'Horde/UI/Tabs.php';
+
+// Exit if this isn't an authenticated user.
+if (!Auth::getAuth()) {
+ header('Location: ' . Horde::applicationUrl('list.php', true));
+ exit;
+}
+
+$vars = Variables::getDefaultVariables();
+$driver = &Skoli_Driver::singleton('');
+$entry = $driver->getEntry($vars->get('entry'));
+if (is_a($entry, 'PEAR_Error') || !count($entry)) {
+ $notification->push(_("Entry not found."), 'horde.error');
+ header('Location: ' . Horde::applicationUrl('search.php', true));
+ exit;
+}
+$share = $GLOBALS['skoli_shares']->getShare($entry['class_id']);
+
+// Check permissions
+if (!$share->hasPermission(Auth::getAuth(), PERMS_READ)) {
+ $notification->push(_("You are not allowed to view this entry."), 'horde.error');
+ header('Location: ' . Util::addParameter(Horde::applicationUrl('search.php', true), 'actionID', 'search'));
+ exit;
+}
+
+$studentdetails = Skoli::getStudent($share->get('address_book'), $entry['student_id']);
+
+// Get view.
+$viewName = Util::getFormData('view', 'Entry');
+
+if ($viewName != 'DeleteEntry') {
+ require_once SKOLI_BASE . '/lib/Forms/Entry.php';
+ if (!$vars->exists('class_id')) {
+ foreach ($entry as $key=>$val) {
+ if (!is_array($val)) {
+ $vars->set($key, $val);
+ }
+ }
+ foreach ($entry['_attributes'] as $key=>$val) {
+ $vars->set('attribute_' . $key, $val);
+ }
+ }
+ $form = new Skoli_EntryForm($vars);
+ if ($viewName == 'EditEntry') {
+ if ($form->validate($vars)) {
+ $driver = &Skoli_Driver::singleton($vars->get('class_id'));
+ $result = $driver->updateEntry($entry['object_id'], $vars);
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push(sprintf(_("Couldn't update this entry: %s"), $result->getMessage()), 'horde.error');
+ } else {
+ $notification->push(sprintf(_("The entry for \"%s\" has been saved."), $studentdetails[$conf['addresses']['name_field']]), 'horde.success');
+ header('Location: ' . Util::addParameter(Horde::applicationUrl('search.php', true), 'actionID', 'search'));
+ exit;
+ }
+ }
+ }
+}
+
+// Entry actions.
+$actionID = Util::getFormData('actionID');
+if ($actionID == 'delete') {
+ if (is_a($deleted = $driver->deleteEntry($entry['object_id']), 'PEAR_Error')) {
+ $notification->push(sprintf(_("There was an error deleting this entry: %s"), $deleted->getMessage()), 'horde.error');
+ } else {
+ $notification->push(sprintf(_("The entry for \"%s\" has been deleted."), $studentdetails[$conf['addresses']['name_field']]), 'horde.success');
+ header('Location: ' . Util::addParameter(Horde::applicationUrl('search.php', true), 'actionID', 'search'));
+ exit;
+ }
+}
+
+// Get tabs.
+$url = Util::addParameter(Horde::applicationUrl('entry.php'), 'entry', $entry['object_id']);
+$tabs = new Horde_UI_Tabs('view', $vars);
+$tabs->addTab(_("View"), $url, array('tabname' => 'Entry', 'id' => 'tabEntry'));
+if ($share->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+ $tabs->addTab(_("Edit"), $url, array('tabname' => 'EditEntry', 'id' => 'tabEditEntry'));
+}
+if ($share->hasPermission(Auth::getAuth(), PERMS_DELETE)) {
+ $tabs->addTab(_("Delete"), $url, array('tabname' => 'DeleteEntry', 'id' => 'tabDeleteEntry'));
+}
+
+$title = _("Edit Entry");
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+
+echo '<div id="page">';
+echo $tabs->render($viewName);
+echo '<h1 class="header">' . sprintf(_("Entry for \"%s\""), $studentdetails[$conf['addresses']['name_field']]) . '</h1>';
+
+// View output
+switch ($viewName) {
+case 'Entry':
+ echo $form->renderInactive($form->getRenderer(), $vars);
+ break;
+
+case 'EditEntry':
+ echo $form->renderActive($form->getRenderer(), $vars, 'entry.php', 'post');
+ break;
+
+case 'DeleteEntry':
+ require SKOLI_TEMPLATES . '/entry/delete.inc';
+ break;
+}
+
+echo '</div>';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/index.php,v 0.1 $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+@define('SKOLI_BASE', dirname(__FILE__));
+$skoli_configured = (is_readable(SKOLI_BASE . '/config/conf.php') &&
+ is_readable(SKOLI_BASE . '/config/prefs.php') &&
+ is_readable(SKOLI_BASE . '/config/schools.php'));
+
+if (!$skoli_configured) {
+ require SKOLI_BASE . '/../lib/Test.php';
+ Horde_Test::configFilesMissing('Skoli', SKOLI_BASE,
+ array('conf.php', 'prefs.php'),
+ array('schools.php' => _('This file defines templates for new classes.')));
+}
+
+require_once SKOLI_BASE . '/lib/base.php';
+require SKOLI_BASE . '/' . $prefs->getValue('initial_page') . '.php';
--- /dev/null
+<?php
+
+$block_name = _("Menu List");
+$block_type = 'tree';
+
+/**
+ * $Horde: skoli/lib/Block/tree_menu.php,v 0.1 $
+ *
+ * @package Horde_Block
+ */
+class Horde_Block_skoli_tree_menu extends Horde_Block {
+
+ var $_app = 'skoli';
+
+ function _buildTree(&$tree, $indent = 0, $parent = null)
+ {
+ global $registry;
+
+ require_once dirname(__FILE__) . '/../base.php';
+
+ $add = Horde::applicationUrl('add.php');
+ $icondir = $registry->getImageDir();
+
+ $classes = Skoli::listClasses(false, PERMS_EDIT);
+ if (count($classes) > 0) {
+
+ $tree->addNode($parent . '__new',
+ $parent,
+ _("New Entry"),
+ $indent + 1,
+ false,
+ array('icon' => 'add.png',
+ 'icondir' => $icondir,
+ 'url' => $add));
+
+ foreach ($classes as $name => $class) {
+ $tree->addNode($parent . $name . '__new',
+ $parent . '__new',
+ sprintf(_("in %s"), $class->get('name')),
+ $indent + 2,
+ false,
+ array('icon' => 'add.png',
+ 'icondir' => $icondir,
+ 'url' => Util::addParameter($add, array('class' => $name))));
+ }
+ $tree->addNode($parent . '__search',
+ $parent,
+ _("Search"),
+ $indent + 1,
+ false,
+ array('icon' => 'search.png',
+ 'icondir' => $registry->getImageDir('horde'),
+ 'url' => Horde::applicationUrl('search.php')));
+ }
+
+ }
+}
--- /dev/null
+<?php
+/**
+ * Skoli_Driver:: defines an API for implementing storage backends for
+ * Skoli.
+ *
+ * $Horde: skoli/lib/Driver.php,v 0.1 $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @package Skoli
+ */
+class Skoli_Driver {
+
+ /**
+ * String containing the current class name.
+ *
+ * @var string
+ */
+ var $_class = '';
+
+ /**
+ * An error message to throw when something is wrong.
+ *
+ * @var string
+ */
+ var $_errormsg;
+
+ /**
+ * Constructor - All real work is done by initialize().
+ */
+ function Skoli_Driver($errormsg = null)
+ {
+ if (is_null($errormsg)) {
+ $this->_errormsg = _("The School backend is not currently available.");
+ } else {
+ $this->_errormsg = $errormsg;
+ }
+ }
+
+ /**
+ * Attempts to return a concrete Skoli_Driver instance based on $driver.
+ *
+ * @param string $class The name of the class to load.
+ *
+ * @param string $driver The type of the concrete Skoli_Driver subclass
+ * to return. The class name is based on the
+ * storage driver ($driver). The code is
+ * dynamically included.
+ *
+ * @param array $params A hash containing any additional configuration
+ * or connection parameters a subclass might need.
+ *
+ * @return Skoli_Driver The newly created concrete Skoli_Driver
+ * instance, or false on an error.
+ */
+ function &factory($class = '', $driver = null, $params = null)
+ {
+ /* Check if we have access to the given class */
+ static $classes;
+ if (!is_array($classes)) {
+ $classes = Skoli::listClasses();
+ }
+ if (!isset($classes[$class])) {
+ $class = &new Skoli_Driver(sprintf(_("Access for class \"%s\" is denied"), $class));
+ return $class;
+ }
+
+ if (is_null($driver)) {
+ $driver = $GLOBALS['conf']['storage']['driver'];
+ }
+ $driver = basename($driver);
+
+ if (is_null($params)) {
+ $params = Horde::getDriverConfig('storage', $driver);
+ }
+
+ require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
+ $objclass = 'Skoli_Driver_' . $driver;
+ if (class_exists($objclass)) {
+ $class = &new $objclass($class, $params);
+ $result = $class->initialize();
+ if (is_a($result, 'PEAR_Error')) {
+ $class = &new Skoli_Driver(sprintf(_("The School backend is not currently available: %s"), $result->getMessage()));
+ }
+ } else {
+ $class = &new Skoli_Driver(sprintf(_("Unable to load the definition of %s."), $objclass));
+ }
+
+ return $class;
+ }
+
+ /**
+ * Attempts to return a reference to a concrete Skoli_Driver
+ * instance based on $driver. It will only create a new instance
+ * if no Skoli_Driver instance with the same parameters currently
+ * exists.
+ *
+ * This should be used if multiple storage sources are required.
+ *
+ * This method must be invoked as: $var = &Skoli_Driver::singleton()
+ *
+ * @param string $class The name of the class to load.
+ *
+ * @param string $driver The type of concrete Skoli_Driver subclass
+ * to return. The is based on the storage
+ * driver ($driver). The code is dynamically
+ * included.
+ *
+ * @param array $params (optional) A hash containing any additional
+ * configuration or connection parameters a
+ * subclass might need.
+ *
+ * @return mixed The created concrete Skoli_Driver instance, or false
+ * on error.
+ */
+ function &singleton($class = '', $driver = null, $params = null)
+ {
+ static $instances = array();
+
+ if (is_null($driver)) {
+ $driver = $GLOBALS['conf']['storage']['driver'];
+ }
+
+ if (is_null($params)) {
+ $params = Horde::getDriverConfig('storage', $driver);
+ }
+
+ $signature = serialize(array($class, $driver, $params));
+ if (!isset($instances[$signature])) {
+ $instances[$signature] = &Skoli_Driver::factory($class, $driver, $params);
+ }
+
+ return $instances[$signature];
+ }
+
+ /**
+ * Generate a universal / unique identifier for an entry. This is
+ * NOT something that we expect to be able to parse into a
+ * entry list.
+ *
+ * @return string A nice unique string (should be 255 chars or less).
+ */
+ function generateUID()
+ {
+ return date('YmdHis') . '.'
+ . substr(str_pad(base_convert(microtime(), 10, 36), 16, uniqid(mt_rand()), STR_PAD_LEFT), -16)
+ . '@' . $GLOBALS['conf']['server']['name'];
+ }
+}
--- /dev/null
+<?php
+/**
+ * Skoli storage implementation for PHP's PEAR database abstraction layer.
+ *
+ * Required parameters:<pre>
+ * 'phptype' The database type (e.g. 'pgsql', 'mysql', etc.).
+ * 'charset' The database's internal charset.</pre>
+ *
+ * Required by some database implementations:<pre>
+ * 'hostspec' The hostname of the database server.
+ * 'protocol' The communication protocol ('tcp', 'unix', etc.).
+ * 'database' The name of the database.
+ * 'username' The username with which to connect to the database.
+ * 'password' The password associated with 'username'.
+ * 'options' Additional options to pass to the database.
+ * 'tty' The TTY on which to connect to the database.
+ * 'port' The port on which to connect to the database.</pre>
+ *
+ * Optional values when using separate reading and writing servers, for example
+ * in replication settings:<pre>
+ * 'splitread' Boolean, whether to implement the separation or not.
+ * 'read' Array containing the parameters which are different for
+ * the read database connection, currently supported
+ * only 'hostspec' and 'port' parameters.</pre>
+ *
+ * Optional parameters:<pre>
+ * 'objects_table' The name of the objects table in 'database'.
+ * Default is 'skoli_objects'.
+ * 'object_attributes_table' The name of the attributes table in 'database'.
+ * Default ist 'skoli_object_attributes'.
+ * 'students_table' The name of the students table in 'database'.
+ * Default is 'skoli_classes_students'.</pre>
+ *
+ * The table structure can be created by the scripts/sql/skoli.sql script.
+ *
+ * $Horde: skoli/lib/Driver/sql.php,v 0.1 $
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @package Skoli
+ */
+class Skoli_Driver_sql extends Skoli_Driver {
+
+ /**
+ * Handle for the current database connection.
+ *
+ * @var DB
+ */
+ var $_db;
+
+ /**
+ * Handle for the current database connection, used for writing. Defaults
+ * to the same handle as $_db if a separate write database is not required.
+ *
+ * @var DB
+ */
+ var $_write_db;
+
+ /**
+ * Constructs a new SQL storage object.
+ *
+ * @param string $classlist The classlist to load.
+ * @param array $params A hash containing connection parameters.
+ */
+ function Skoli_Driver_sql($class, $params = array())
+ {
+ $this->_class = $class;
+ $this->_params = $params;
+ }
+
+ /**
+ * Attempts to open a connection to the SQL server.
+ *
+ * @return boolean True on success, PEAR_Error on failure.
+ */
+ function initialize()
+ {
+ Horde::assertDriverConfig($this->_params, 'storage',
+ array('phptype', 'charset'));
+
+ if (!isset($this->_params['database'])) {
+ $this->_params['database'] = '';
+ }
+ if (!isset($this->_params['username'])) {
+ $this->_params['username'] = '';
+ }
+ if (!isset($this->_params['hostspec'])) {
+ $this->_params['hostspec'] = '';
+ }
+ if (!isset($this->_params['objects_table'])) {
+ $this->_params['objects_table'] = 'skoli_objects';
+ }
+ if (!isset($this->_params['object_attributes_table'])) {
+ $this->_params['object_attributes_table'] = 'skoli_object_attributes';
+ }
+ if (!isset($this->_params['students_table'])) {
+ $this->_params['students_table'] = 'skoli_classes_students';
+ }
+
+ /* Connect to the SQL server using the supplied parameters. */
+ $this->_write_db = &DB::connect($this->_params,
+ array('persistent' => !empty($this->_params['persistent'])));
+ if (is_a($this->_write_db, 'PEAR_Error')) {
+ return $this->_write_db;
+ }
+
+ /* Set DB portability options. */
+ switch ($this->_write_db->phptype) {
+ case 'mssql':
+ $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+ break;
+ default:
+ $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+ }
+
+ /* Check if we need to set up the read DB connection
+ * seperately. */
+ if (!empty($this->_params['splitread'])) {
+ $params = array_merge($this->_params, $this->_params['read']);
+ $this->_db = &DB::connect($params,
+ array('persistent' => !empty($params['persistent'])));
+ if (is_a($this->_db, 'PEAR_Error')) {
+ return $this->_db;
+ }
+
+ /* Set DB portability options. */
+ switch ($this->_db->phptype) {
+ case 'mssql':
+ $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+ break;
+ default:
+ $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+ }
+
+ } else {
+ /* Default to the same DB handle for the writer too. */
+ $this->_db =& $this->_write_db;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get all students from the backend storage.
+ *
+ * @return array List with all student IDs.
+ */
+ function getStudents()
+ {
+ $query = 'SELECT student_id FROM ' . $this->_params['students_table'] .
+ ' WHERE class_id = ?';
+ $values = array($this->_class);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getStudents(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $students = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($students, 'PEAR_Error')) {
+ Horde::logMessage($students, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $students;
+ }
+
+ return $students;
+ }
+
+ /**
+ * Add students to the backend storage.
+ *
+ * @param array $students List with students.
+ *
+ * @return boolean True on success, PEAR_Error on failure.
+ */
+ function addStudents($students)
+ {
+ /* Delete any existing Students */
+ $query = 'DELETE FROM ' . $this->_params['students_table'] .
+ ' WHERE class_id=?';
+ $result = $this->_write_db->query($query, array($this->_class));
+
+ foreach ($students as $addressid) {
+ $query = 'INSERT INTO ' . $this->_params['students_table'] .
+ ' (class_id, student_id)' .
+ ' VALUES (?, ?)';
+ $values = array($this->_class, $addressid);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::addStudents(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the insertion query. */
+ $result = $this->_write_db->query($query, $values);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get an entry from the backend storage.
+ *
+ * @param string $entryid The entry ID.
+ *
+ * @return array List with all entry fields.
+ */
+ function getEntry($entryid)
+ {
+ $query = 'SELECT * FROM ' . $this->_params['objects_table'] .
+ ' WHERE object_id = ?';
+ $values = array($entryid);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $entry = $this->_db->getRow($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($entry, 'PEAR_Error')) {
+ Horde::logMessage($entry, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $entry;
+ } else if (!is_array($entry)) {
+ return array();
+ }
+
+ $query = 'SELECT * FROM ' . $this->_params['object_attributes_table'] .
+ ' WHERE object_id = ?';
+ $values = array($entryid);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $attributes = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($attributes, 'PEAR_Error')) {
+ Horde::logMessage($attributes, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $attributes;
+ }
+
+ $entry['_attributes'] = array();
+ foreach ($attributes as $attribute) {
+ $entry['_attributes'][$attribute['attr_name']] = $attribute['attr_value'];
+ }
+
+ if (empty($this->_class)) {
+ $this->_class = $entry['class_id'];
+ }
+
+ return $entry;
+ }
+
+ /**
+ * Get all entries for the current class or student from the backend storage.
+ *
+ * @param string $studentid The student ID.
+ *
+ * @param string $type The entry type to search in.
+ *
+ * @param array $searchparams Some additional search parameters.
+ *
+ * @return array List with all entries.
+ */
+ function getEntries($studentid = null, $type = null, $searchparams = array())
+ {
+ if (is_null($studentid)) {
+ $students = $this->getStudents();
+ } else {
+ $students = array(array('student_id' => $studentid));
+ }
+
+ foreach ($students as $studentkey=>$student) {
+ /* Build the search parameter */
+ if (count($searchparams)) {
+ $where = '';
+ $search_values = array();
+ if (count($searchparams) === 1 && !is_array($searchparams[0])) {
+ /* search all attributes for the specified text */
+ $where = ' AND a.attr_value LIKE ?';
+ $search_values[] = '%' . $searchparams[0] . '%';
+ } else {
+ /* search only in the specified fields */
+ $where = ' AND ';
+ for ($i = 0; $i < count($searchparams); $i++) {
+ $strict = !empty($searchparams[$i]['strict']);
+ $where .= '(a.attr_name = ? AND a.attr_value ' . ($strict ? '=' : 'LIKE') . ' ?) OR ';
+ $search_values[] = $searchparams[$i]['name'];
+ if ($strict) {
+ $search_values[] = $searchparams[$i]['value'];
+ } else {
+ $search_values[] = '%' . $searchparams[$i]['value'] . '%';
+ }
+ }
+ $where = substr($where, 0, -4);
+ }
+ $query = 'SELECT o.* FROM ' . $this->_params['object_attributes_table'] . ' AS a, ' .
+ $this->_params['objects_table'] . ' AS o' .
+ ' WHERE o.object_id = a.object_id AND o.class_id = ? AND o.student_id = ?' . (!is_null($type) ? ' AND o.object_type = ?' : '') . $where .
+ ' GROUP BY o.object_id';
+ $values = array($this->_class, $student['student_id']);
+ if (!is_null($type)) {
+ $values[] = $type;
+ }
+ $values = array_merge($values, $search_values);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getEntries(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $entries = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($entries, 'PEAR_Error')) {
+ Horde::logMessage($entries, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $entries;
+ }
+ } else {
+ $query = 'SELECT * FROM ' . $this->_params['objects_table'] .
+ ' WHERE class_id = ? AND student_id = ?' .
+ (!is_null($type) ? ' AND object_type = ?' : '');
+ $values = array($this->_class, $student['student_id']);
+ if (!is_null($type)) {
+ $values[] = $type;
+ }
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getEntries(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $entries = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($entries, 'PEAR_Error')) {
+ Horde::logMessage($entries, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $entries;
+ }
+ }
+
+ $students[$studentkey]['_entries'] = $entries;
+
+ foreach ($entries as $entrykey=>$entry) {
+ $query = 'SELECT * FROM ' . $this->_params['object_attributes_table'] .
+ ' WHERE object_id = ?';
+ $values = array($entry['object_id']);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getEntries(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $attributes = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($attributes, 'PEAR_Error')) {
+ Horde::logMessage($attributes, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $attributes;
+ }
+
+ $students[$studentkey]['_entries'][$entrykey]['_attributes'] = array();
+ foreach ($attributes as $attribute) {
+ $students[$studentkey]['_entries'][$entrykey]['_attributes'][$attribute['attr_name']] = $attribute['attr_value'];
+ }
+ }
+ }
+
+ return $students;
+ }
+
+ /**
+ * Get the timestamp of the last entry for the given student.
+ *
+ * @param string $studentid The student ID.
+ *
+ * @return int The last entry.
+ */
+ function lastEntry($studentid)
+ {
+ $query = 'SELECT object_time FROM ' . $this->_params['objects_table'] .
+ ' WHERE class_id = ? AND student_id = ? ORDER BY object_time DESC LIMIT 1';
+ $values = array($this->_class, $studentid);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::lastEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $lastentry = $this->_db->getRow($query, $values, DB_FETCHMODE_ORDERED);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($lastentry, 'PEAR_Error')) {
+ Horde::logMessage($lastentry, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $lastentry;
+ }
+
+ if (count($lastentry)) {
+ return $lastentry[0];
+ }
+
+ return null;
+ }
+
+ /**
+ * Add or update a new entry to the backend storage.
+ *
+ * @param string $entryid The entry ID.
+ *
+ * @param Variables $vars List with form variables.
+ *
+ * @return boolean True on success, PEAR_Error on failure.
+ */
+ function updateEntry($entryid, $vars)
+ {
+ $attributes = array();
+ foreach ($vars->_vars as $key=>$value) {
+ if (strpos($key, 'attribute_') === 0 && $value != '') {
+ $attribute = substr($key, 10);
+ $attributes[$attribute] = $value;
+ }
+ }
+
+ $query = 'UPDATE ' . $this->_params['objects_table'] . ' SET' .
+ ' class_id = ?, student_id = ?, object_time = ?, object_type = ?' .
+ ' WHERE object_id = ?';
+ require_once 'Horde/Date.php';
+ $date = new Horde_Date($vars->get('object_time'));
+ $values = array($this->_class, $vars->get('student_id'), $date->datestamp(), $vars->get('object_type'), $entryid);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::updateEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the insertion query. */
+ $result = $this->_write_db->query($query, $values);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+
+ $query = 'DELETE FROM ' . $this->_params['object_attributes_table'] .
+ ' WHERE object_id = ?';
+ $values = array($entryid);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::updateEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the delete query. */
+ $result = $this->_write_db->query($query, $values);
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ foreach ($attributes as $attribute=>$value) {
+ $query = 'INSERT INTO ' . $this->_params['object_attributes_table'] .
+ ' (object_id, attr_name, attr_value)' .
+ ' VALUES (?, ?, ?)';
+ $values = array($entryid, $attribute, $value);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::addEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the insertion query. */
+ $result = $this->_write_db->query($query, $values);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a new entry to the backend storage.
+ *
+ * @param Variables $vars List with form variables.
+ *
+ * @return Mixed Studentnames on success, PEAR_Error on failure.
+ */
+ function addEntry($vars)
+ {
+ $names = '';
+ $class = current(Skoli::listStudents($this->_class));
+
+ $attributes = array();
+ foreach ($vars->_vars as $key=>$value) {
+ if (strpos($key, 'attribute_') === 0 && $value != '') {
+ $attribute = substr($key, 10);
+ $attributes[$attribute] = $value;
+ }
+ }
+
+ require_once 'Horde/Date.php';
+ foreach ($vars->get('student_id') as $studentid) {
+ $query = 'INSERT INTO ' . $this->_params['objects_table'] .
+ ' (object_id, object_owner, object_uid, class_id, student_id, object_time, object_type)' .
+ ' VALUES (?, ?, ?, ?, ?, ?, ?)';
+ $entryId = md5(uniqid(mt_rand(), true));
+ $date = new Horde_Date($vars->get('object_time'));
+ $values = array($entryId, Auth::getAuth(), $this->generateUID(), $this->_class, $studentid, $date->datestamp(), $vars->get('object_type'));
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::addEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the insertion query. */
+ $result = $this->_write_db->query($query, $values);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+
+ foreach ($attributes as $attribute=>$value) {
+ $query = 'INSERT INTO ' . $this->_params['object_attributes_table'] .
+ ' (object_id, attr_name, attr_value)' .
+ ' VALUES (?, ?, ?)';
+ $values = array($entryId, $attribute, $value);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::addEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the insertion query. */
+ $result = $this->_write_db->query($query, $values);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+ }
+
+ $studentdetails = Skoli::getStudent($class['address_book'], $studentid);
+ $names .= $studentdetails[$GLOBALS['conf']['addresses']['name_field']] . ', ';
+ }
+
+ return substr($names, 0, -2);
+ }
+
+ /**
+ * Get all currently used subjects from the current class.
+ *
+ * @param string $type Get subjects only from this type.
+ *
+ * @return array List with all subjects.
+ */
+ function getSubjects($type = null)
+ {
+ $where = !is_null($type) ? ' AND o.object_type = ?' : '';
+ $query = 'SELECT DISTINCT a.attr_value FROM ' . $this->_params['object_attributes_table'] . ' AS a, ' .
+ $this->_params['objects_table'] . ' AS o' .
+ ' WHERE a.object_id = o.object_id AND o.class_id = ? AND a.attr_name = ?' . $where;
+ $values = array($this->_class, 'subject');
+ if (!is_null($type)) {
+ $values[] = $type;
+ }
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::getSubjects(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $subjects = $this->_db->getAll($query, $values, DB_FETCHMODE_ORDERED);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($subjects, 'PEAR_Error')) {
+ Horde::logMessage($subjects, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $subjects;
+ }
+
+ $subjectlist = array();
+ foreach ($subjects as $subject) {
+ $subjectlist[] = $subject[0];
+ }
+
+ return $subjectlist;
+ }
+
+ /**
+ * Deletes all data from the current class.
+ *
+ * @return boolean True on success, PEAR_Error on failure.
+ */
+ function deleteAll()
+ {
+ $query = 'DELETE FROM ' . $this->_params['students_table'] .
+ ' WHERE class_id = ?';
+ $values = array($this->_class);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::deleteAll(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the delete query. */
+ $result = $this->_write_db->query($query, $values);
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $query = 'SELECT object_id FROM ' . $this->_params['objects_table'] .
+ ' WHERE class_id = ?';
+ $values = array($this->_class);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::deleteAll(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the select query. */
+ $entries = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($entries, 'PEAR_Error')) {
+ Horde::logMessage($entries, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $entries;
+ }
+
+ foreach ($entries as $entry) {
+ $result = $this->deleteEntry($entry['object_id']);
+
+ /* Return an error immediately if the query failed. */
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Deletes an entry from the current class.
+ *
+ * @param string $object_id The entry ID to delete.
+ *
+ * @return boolean True on success, PEAR_Error on failure.
+ */
+ function deleteEntry($object_id)
+ {
+ $query = 'DELETE FROM ' . $this->_params['objects_table'] .
+ ' WHERE object_id = ? AND class_id = ?';
+ $values = array($object_id, $this->_class);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::deleteEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the delete query. */
+ $result = $this->_write_db->query($query, $values);
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $query = 'DELETE FROM ' . $this->_params['object_attributes_table'] .
+ ' WHERE object_id = ?';
+ $values = array($object_id);
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Skoli_Driver_sql::deleteEntry(): %s', $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Attempt the delete query. */
+ $result = $this->_write_db->query($query, $values);
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return true;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Horde_Form for creating classes.
+ *
+ * $Horde: skoli/lib/Forms/CreateClass.php,v 0.1 $
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @package Skoli
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Skoli_CreateClassForm class provides the form for
+ * creating a class.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @package Skoli
+ */
+class Skoli_CreateClassForm extends Horde_Form {
+
+ /**
+ * Name of the new share.
+ *
+ * @var int
+ */
+ var $shareid;
+
+ /**
+ * List of school properties.
+ *
+ * @var array
+ */
+ var $_schoolproperties = array(
+ 'grade',
+ 'semester',
+ 'start',
+ 'end',
+ 'location',
+ 'marks');
+
+
+ function Skoli_CreateClassForm(&$vars)
+ {
+ global $conf, $prefs, $registry;
+
+ parent::Horde_Form($vars, _("Create Class"));
+
+ $this->setSection('properties', _("Properties"));
+
+ $this->addVariable(_("General Settings"), null, 'header', false);
+
+ $this->addVariable(_("Name"), 'name', 'text', true);
+ $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60));
+
+ $this->addVariable(_("Category"), 'category', 'category', false);
+ // A new category doesn't survive a reload action, so reset it
+ // @TODO: Could this be a bug?
+ if (strpos($this->_vars->get('category'), '*new*') !== false) {
+ $this->_vars->set('category', $this->_vars->get('new_category'));
+ }
+
+ $this->addVariable(_("School Specific Settings"), null, 'header', false);
+
+ // Load Skoli_School
+ require_once SKOLI_BASE . '/lib/School.php';
+
+ // List schools
+ $schoollist = Skoli_School::listSchools();
+ $actionvariable = &$this->addVariable(_("Schools"), 'school', 'enum', true, count($schoollist)>1 ? false : true, null, array($schoollist, _("Choose:")));
+ if (count($schoollist) > 1) {
+ require_once 'Horde/Form/Action.php';
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ } else {
+ $this->_vars->set('school', key($schoollist));
+ }
+
+ // Load the selected school
+ if ($this->_vars->exists('school')) {
+ $school = new Skoli_School($this->_vars->get('school'));
+ foreach ($this->_schoolproperties as $name) {
+ $school->addFormVariable($this, $name);
+ }
+ }
+
+ $this->setSection('students', _("Students"));
+
+ $this->addVariable(_("Address Book"), null, 'header', false);
+
+ $addressbooklist = Skoli_School::listAddressBooks();
+ $actionvariable = &$this->addVariable(_("Address Book"), 'address_book', 'enum', true, count($addressbooklist)>1 ? false : true, null, array($addressbooklist, _("Choose:")));
+ if (count($addressbooklist) > 1) {
+ require_once 'Horde/Form/Action.php';
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ } else {
+ $this->_vars->set('address_book', key($addressbooklist));
+ }
+
+ $this->addVariable(_("Students"), null, 'header', false);
+
+ if ($this->_vars->get('address_book') != '') {
+ $searchargs = array(
+ 'addresses' => array(''),
+ 'addressbooks' => array($this->_vars->get('address_book')),
+ 'fields' => array()
+ );
+ if ($search_fields_pref = $prefs->getValue('search_fields')) {
+ foreach (explode("\n", $search_fields_pref) as $s) {
+ $s = trim($s);
+ $s = explode("\t", $s);
+ if (!empty($s[0]) && ($s[0] == $this->_vars->get('address_book'))) {
+ $searchargs['fields'][array_shift($s)] = $s;
+ break;
+ }
+ }
+ }
+ $resultstmp = $registry->call('contacts/search', $searchargs);
+ // contacts/search seems to return an array entry for each source.
+ $results = array();
+ foreach ($resultstmp as $r) {
+ $results = array_merge($results, $r);
+ }
+ foreach ($results as $address) {
+ if (isset($address['__type']) && $address['__type'] == 'Object') {
+ $addresses[$address['__key']] = $address[$conf['addresses']['name_field']];
+ }
+ }
+ } else {
+ $addresses = array();
+ }
+
+ $this->addVariable(_("Students"), 'students', 'multienum', false, false, null, array($addresses, 20));
+
+ if ($conf['addresses']['contact_list'] != 'none' && $prefs->getValue('contact_list') != 'none') {
+ $this->addVariable(_("Contact List"), null, 'header', false);
+ if ($conf['addresses']['contact_list'] == 'user' && $prefs->getValue('contact_list') == 'ask') {
+ $this->addVariable(_("Create Contact List?"), 'contact_list_create', 'boolean', true, false);
+ if (!$this->_vars->exists('contact_list')) {
+ $this->_vars->set('contact_list_create', true);
+ }
+ }
+ $this->addVariable(_("Name"), 'contact_list', 'text', false,
+ $conf['addresses']['contact_list'] == 'auto' || $prefs->getValue('contact_list') == 'auto' ? true : false, _("The substitutions %c, %g or %s will be replaced automatically by the class, grade respectively semester name."));
+ if (!$this->_vars->exists('contact_list')) {
+ $contactlist = $conf['addresses']['contact_list'] == 'auto' ? $conf['addresses']['contact_list_name'] : $prefs->getValue('contact_list_name');
+ } else {
+ $contactlist = $this->_vars->get('contact_list');
+ }
+ $this->_vars->set('contact_list', Skoli_School::parseContactListName($contactlist, $this->_vars));
+ }
+
+ $this->setButtons(array(_("Create")));
+ }
+
+ function execute()
+ {
+ global $conf, $prefs, $registry, $notification;
+
+ /* Add new category. */
+ if (strpos($this->_vars->get('category'), '*new*') !== false || $this->_vars->get('category') == $this->_vars->get('new_category')) {
+ require_once 'Horde/Prefs/CategoryManager.php';
+ $cManager = new Prefs_CategoryManager();
+ $cManager->add($this->_vars->get('new_category'));
+ $this->_vars->set('category', $this->_vars->get('new_category'));
+ }
+
+ // Create new share.
+ $this->shareid = md5(microtime());
+ $class = $GLOBALS['skoli_shares']->newShare($this->shareid);
+ if (is_a($class, 'PEAR_Error')) {
+ return $class;
+ }
+ $class->set('name', $this->_vars->get('name'));
+ $class->set('desc', $this->_vars->get('description'));
+ $class->set('category', $this->_vars->get('category'));
+ $class->set('school', $this->_vars->get('school'));
+ $class->set('address_book', $this->_vars->get('address_book'));
+
+ require_once 'Horde/Date.php';
+ foreach ($this->_schoolproperties as $property) {
+ if ($property == 'start' || $property == 'end') {
+ $date = new Horde_Date($this->_vars->get($property));
+ $this->_vars->set($property, $date->datestamp());
+ } else if ($property == 'marks' && $this->_vars->get($property . '_custom') != '') {
+ $this->_vars->set($property, $this->_vars->get($property . '_custom'));
+ }
+ $class->set($property, $this->_vars->get($property) == '' ? null : $this->_vars->get($property));
+ }
+
+ // Save students
+ if ($this->_vars->exists('students')) {
+ $driver = &Skoli_Driver::singleton($this->shareid);
+ $result = $driver->addStudents($this->_vars->get('students'));
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push(_("Couldn't add the selected students to the class."), 'horde.warning');
+ }
+
+ // Add new contact list
+ if ($conf['addresses']['contact_list'] != 'none' && $prefs->getValue('contact_list') != 'none' && $this->_vars->get('contact_list') != '') {
+ $createlist = true;
+ if ($conf['addresses']['contact_list'] == 'user' && $prefs->getValue('contact_list') == 'ask' && $this->_vars->get('contact_list_create') == '') {
+ $createlist = false;
+ }
+ } else {
+ $createlist = false;
+ }
+ if ($createlist) {
+ $apiargs = array(
+ 'content' => array(
+ '__type' => 'Group',
+ '__members' => serialize($this->_vars->get('students')),
+ 'name' => Skoli_School::parseContactListName($this->_vars->get('contact_list'), $this->_vars, true),
+ ),
+ 'contentType' => 'array',
+ 'source' => $this->_vars->get('address_book')
+ );
+ $result = $registry->call('contacts/import', $apiargs);
+ if ($result === false || is_a($result, 'PEAR_Error')) {
+ $notification->push(sprintf(_("Couldn't create the contact list \"%s\"."), $this->_vars->get('contact_list')), 'horde.warning');
+ }
+ }
+ }
+
+ return $GLOBALS['skoli_shares']->addShare($class);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_Form for deleting classs.
+ *
+ * $Horde: skoli/lib/Forms/DeleteClass.php,v 0.1 $
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @package Skoli
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Skoli_DeleteClassForm class provides the form for
+ * deleting a class.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @since Skoli 2.2
+ * @package Skoli
+ */
+class Skoli_DeleteClassForm extends Horde_Form {
+
+ /**
+ * Class being deleted
+ */
+ var $_class;
+
+ function Skoli_DeleteClassForm(&$vars, &$class)
+ {
+ $this->_class = &$class;
+ parent::Horde_Form($vars, sprintf(_("Delete %s"), $class->get('name')));
+
+ $this->addHidden('', 'c', 'text', true);
+ $this->addVariable(sprintf(_("Really delete the class \"%s\"? This cannot be undone and all data on this class will be permanently removed."), $this->_class->get('name')), 'desc', 'description', false);
+
+ $this->setButtons(array(_("Delete"), _("Cancel")));
+ }
+
+ function execute()
+ {
+ // If cancel was clicked, return false.
+ if ($this->_vars->get('submitbutton') == _("Cancel")) {
+ return false;
+ }
+
+ if ($this->_class->get('owner') != Auth::getAuth()) {
+ return PEAR::raiseError(_("Permission denied"));
+ }
+
+ // Delete the class.
+ $storage = &Skoli_Driver::singleton($this->_class->getName());
+ $result = $storage->deleteAll();
+ if (is_a($result, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Unable to delete \"%s\": %s"), $this->_class->get('name'), $result->getMessage()));
+ } else {
+ // Remove share and all groups/permissions.
+ $result = $GLOBALS['skoli_shares']->removeShare($this->_class);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } else {
+ // Remove class from the display list if it exists
+ $key = array_search($this->_class->getName(), $GLOBALS['display_classes']);
+ if ($key !== false) {
+ unset($GLOBALS['display_classes'][$key]);
+ $GLOBALS['prefs']->setValue('display_classes', serialize($GLOBALS['display_classes']));
+ }
+ }
+ }
+
+ return true;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_Form for editing classs.
+ *
+ * $Horde: skoli/lib/Forms/EditClass.php,v 0.1 $
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @package Skoli
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Skoli_EditClassForm class provides the form for
+ * editing a class.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @since Skoli 2.2
+ * @package Skoli
+ */
+class Skoli_EditClassForm extends Horde_Form {
+
+ /**
+ * Class being edited
+ */
+ var $_class;
+
+ /**
+ * List of school properties.
+ *
+ * @var array
+ */
+ var $_schoolproperties = array(
+ 'grade',
+ 'semester',
+ 'start',
+ 'end',
+ 'location',
+ 'marks');
+
+ function Skoli_EditClassForm(&$vars, &$class)
+ {
+ global $conf, $prefs, $registry;
+
+ $this->_class = &$class;
+
+ parent::Horde_Form($vars, sprintf(_("Edit %s"), $class->get('name')));
+
+ $this->addHidden('', 'c', 'text', true);
+
+ $this->setSection('properties', _("Properties"));
+
+ $this->addVariable(_("General Settings"), null, 'header', false);
+
+ $this->addVariable(_("Name"), 'name', 'text', true);
+ $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60));
+
+ $this->addVariable(_("Category"), 'category', 'category', false);
+
+ $this->addVariable(_("School Specific Settings"), null, 'header', false);
+
+ // Load Skoli_School
+ require_once SKOLI_BASE . '/lib/School.php';
+
+ // List schools
+ $schoollist = Skoli_School::listSchools();
+ $this->addVariable(_("School"), 'school', 'enum', true, true, null, array($schoollist, _("Choose:")));
+
+ $school = new Skoli_School($this->_vars->get('school'));
+ foreach ($this->_schoolproperties as $name) {
+ $school->addFormVariable($this, $name);
+ }
+
+ $this->setSection('students', _("Students"));
+
+ $this->addVariable(_("Address Book"), null, 'header', false);
+
+ $addressbooklist = Skoli_School::listAddressBooks(true);
+ $actionvariable = &$this->addVariable(_("Address Book"), 'address_book', 'enum', true, count($addressbooklist)>1 ? false : true, null, array($addressbooklist, _("Choose:")));
+ if (count($addressbooklist) > 1) {
+ require_once 'Horde/Form/Action.php';
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ } else {
+ $this->_vars->set('address_book', key($addressbooklist));
+ }
+
+ $this->addVariable(_("Students"), null, 'header', false);
+
+ if ($this->_vars->get('address_book') != '') {
+ $searchargs = array(
+ 'addresses' => array(''),
+ 'addressbooks' => array($this->_vars->get('address_book')),
+ 'fields' => array()
+ );
+ if ($search_fields_pref = $prefs->getValue('search_fields')) {
+ foreach (explode("\n", $search_fields_pref) as $s) {
+ $s = trim($s);
+ $s = explode("\t", $s);
+ if (!empty($s[0]) && ($s[0] == $this->_vars->get('address_book'))) {
+ $searchargs['fields'][array_shift($s)] = $s;
+ break;
+ }
+ }
+ }
+ $resultstmp = $registry->call('contacts/search', $searchargs);
+ // contacts/search seems to return an array entry for each source.
+ $results = array();
+ foreach ($resultstmp as $r) {
+ $results = array_merge($results, $r);
+ }
+ foreach ($results as $address) {
+ if (isset($address['__type']) && $address['__type'] == 'Object') {
+ $addresses[$address['__key']] = $address[$conf['addresses']['name_field']];
+ }
+ }
+ } else {
+ $addresses = array();
+ }
+
+ $this->addVariable(_("Students"), 'students', 'multienum', false, false, null, array($addresses, 20));
+
+ $this->setButtons(array(_("Save")));
+ }
+
+ function execute()
+ {
+ global $conf, $prefs, $registry, $notification;
+
+ /* Add new category. */
+ if (strpos($this->_vars->get('category'), '*new*') !== false || $this->_vars->get('category') == $this->_vars->get('new_category')) {
+ require_once 'Horde/Prefs/CategoryManager.php';
+ $cManager = new Prefs_CategoryManager();
+ $cManager->add($this->_vars->get('new_category'));
+ $this->_vars->set('category', $this->_vars->get('new_category'));
+ }
+
+ $this->_class->set('name', $this->_vars->get('name'));
+ $this->_class->set('desc', $this->_vars->get('description'));
+ $this->_class->set('category', $this->_vars->get('category'));
+ $this->_class->set('address_book', $this->_vars->get('address_book'));
+
+ require_once 'Horde/Date.php';
+ foreach ($this->_schoolproperties as $property) {
+ if ($property == 'start' || $property == 'end') {
+ $date = new Horde_Date($this->_vars->get($property));
+ $this->_vars->set($property, $date->datestamp());
+ } else if ($property == 'marks' && $this->_vars->get($property . '_custom') != '') {
+ $this->_vars->set($property, $this->_vars->get($property . '_custom'));
+ }
+ $this->_class->set($property, $this->_vars->get($property) == '' ? null : $this->_vars->get($property));
+ }
+
+ // Save students
+ $driver = &Skoli_Driver::singleton($this->_vars->get('c'));
+ $result = $driver->addStudents($this->_vars->get('students'));
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push(_("Couldn't add the selected students to the class."), 'horde.warning');
+ }
+
+ $result = $this->_class->save();
+ if (is_a($result, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Unable to save class \"%s\": %s"), $id, $result->getMessage()));
+ }
+ return true;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_Form for adding and updateing entries.
+ *
+ * $Horde: skoli/lib/Forms/Entry.php,v 0.1 $
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @package Skoli
+ */
+
+/** Variables */
+require_once 'Horde/Variables.php';
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Skoli_EntryForm class provides the form for
+ * adding and updateing a new entry.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @package Skoli
+ */
+class Skoli_EntryForm extends Horde_Form {
+
+ function Skoli_EntryForm(&$vars)
+ {
+ global $conf, $prefs, $registry;
+
+ $update = $vars->exists('entry') && $vars->exists('entry');
+
+ parent::Horde_Form($vars, $update ? _("Update Entry") : _("Add Entry"));
+
+ if ($update) {
+ $this->addHidden('', 'entry', 'text', true);
+ $this->addHidden('', 'view', 'text', true);
+ }
+
+ $classes = Skoli::listClasses(false, PERMS_EDIT);
+ $classes_enums = array();
+ foreach ($classes as $class) {
+ if ($class->hasPermission(Auth::getAuth(), PERMS_EDIT)) {
+ $classes_enums[$class->getName()] = $class->get('name');
+ }
+ }
+
+ if (!$this->_vars->exists('class_id') && $vars->exists('class')) {
+ $this->_vars->set('class_id', $vars->get('class'));
+ if (!$this->_vars->exists('student_id') && $vars->exists('student')) {
+ $this->_vars->set('student_id', array($vars->get('student')));
+ } else {
+ $this->_vars->set('student_id', array());
+ }
+ }
+
+ // List classes
+ $actionvariable = &$this->addVariable(_("Class"), 'class_id', 'enum', true, count($classes)>1 ? false : true, null, array($classes_enums, _("Choose:")));
+ if (count($classes) > 1) {
+ require_once 'Horde/Form/Action.php';
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ } else {
+ reset($classes);
+ $this->_vars->set('class_id', key($classes));
+ }
+
+ // Load the selected students
+ if ($this->_vars->get('class_id') != '') {
+ $class = current(Skoli::listStudents($vars->get('class_id')));
+ foreach ($class['_students'] as $address) {
+ $addresses[$address['student_id']] = $address[$conf['addresses']['name_field']];
+ }
+ if ($update) {
+ $this->addVariable(_("Student"), 'student_id', 'enum', true, false, null, array($addresses));
+ } else {
+ $this->addVariable(_("Student"), 'student_id', 'multienum', true, false, null, array($addresses, 14));
+ }
+ } else {
+ $addresses = array();
+ }
+
+ $this->addVariable(_("Date"), 'object_time', 'monthdayyear', true, false, null, array());
+ if (!$this->_vars->exists('object_time')) {
+ $date = new Horde_Date(time());
+ $this->_vars->set('object_time', array('month' => $date->month, 'day' => $date->mday, 'year' => $date->year));
+ }
+
+ // Load last type from preferences
+ if (!$this->_vars->exists('object_type')) {
+ $this->_vars->set('object_type', $prefs->getValue('default_objects_format'));
+ }
+ if ($conf['objects']['allow_marks']) {
+ $types['mark'] = _("Mark");
+ }
+ if ($conf['objects']['allow_objectives']) {
+ $types['objective'] = _("Objective");
+ }
+ if ($conf['objects']['allow_outcomes']) {
+ $types['outcome'] = _("Outcome");
+ }
+ if ($conf['objects']['allow_absences']) {
+ $types['absence'] = _("Absence");
+ }
+ $actionvariable = &$this->addVariable(_("Type"), 'object_type', 'radio', true, false, null, array($types));
+ require_once 'Horde/Form/Action.php';
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+
+ if ($this->_vars->get('object_type') != '' && $this->_vars->get('class_id') != '') {
+ switch ($this->_vars->get('object_type')) {
+
+ case 'mark':
+ $this->addVariable(_("Title"), 'attribute_title', 'text', true, false);
+ switch ($class['marks']) {
+ case 'numbers':
+ $this->addVariable(_("Mark"), 'attribute_mark', 'number', true, false, _("Mark in numbers"));
+ break;
+
+ case 'percent':
+ $this->addVariable(_("Mark"), 'attribute_mark', 'number', true, false, _("Mark in percent"));
+ break;
+
+ default:
+ $marks_enums = preg_split('/\s*,\s*/', $class['marks']);
+ $this->addVariable(_("Mark"), 'attribute_mark', 'enum', true, false, null, array(array_combine($marks_enums, $marks_enums), _("Choose:")));
+ }
+ // Load Skoli_School
+ require_once SKOLI_BASE . '/lib/School.php';
+ $school = new Skoli_School($class['school']);
+ $school->addFormVariable($this, 'subject', array(true));
+ $this->addVariable(_("Weight"), 'attribute_weight', 'number', true, false);
+ if (!$this->_vars->exists('attribute_weight')) {
+ $this->_vars->set('attribute_weight', 1);
+ }
+ break;
+
+ case 'objective':
+ // Load Skoli_School
+ require_once SKOLI_BASE . '/lib/School.php';
+ $school = new Skoli_School($class['school']);
+ $school->addFormVariable($this, 'subject', array(false, true));
+ $school->addFormVariable($this, 'category', array($this->_vars->get('attribute_subject')));
+ $this->addVariable(_("Objective"), 'attribute_objective', 'longtext', true, false, null, array(4, 60));
+ break;
+
+ case 'outcome':
+ $this->addVariable(_("Outcome"), 'attribute_outcome', 'longtext', true, false, null, array(2, 60));
+ $this->addVariable(_("Completed?"), 'attribute_completed', 'boolean', true, false);
+ $this->addVariable(_("Comment"), 'attribute_comment', 'longtext', false, false, null, array(4, 60));
+ break;
+
+ case 'absence':
+ $this->addVariable(_("Absence"), 'attribute_absence', 'number', true, false, _("Absence in number of lessons"));
+ $this->addVariable(_("Excused?"), 'attribute_excused', 'boolean', true, false);
+ if (!$this->_vars->exists('attribute_absence')) {
+ $this->_vars->set('attribute_excused', true);
+ }
+ $this->addVariable(_("Comment"), 'attribute_comment', 'longtext', false, false, null, array(4, 60));
+ break;
+ }
+ }
+
+ $this->setButtons(array($update ? _("Save") : _("Add")));
+ }
+
+ function execute()
+ {
+ global $conf, $prefs, $registry, $notification;
+
+ // Save last type to preferences
+ $prefs->setValue('default_objects_format', $this->_vars->get('object_type'));
+
+ $driver = &Skoli_Driver::singleton($this->_vars->get('class_id'));
+ $result = $driver->addEntry($this->_vars);
+ if (is_a($result, 'PEAR_Error')) {
+ $notification->push(_("Couldn't add the new entry."), 'horde.warning');
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Skoli School Class.
+ *
+ * $Horde: skoli/lib/School.php,v 0.1 $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @package Skoli
+ */
+class Skoli_School {
+
+ /**
+ * School list from template.
+ *
+ * @var array
+ */
+ public static $schools;
+
+ /**
+ * Current school from template.
+ *
+ * @var array
+ */
+ var $school;
+
+ /**
+ * Load the school list from template.
+ */
+ function Skoli_School($schoolName)
+ {
+ self::_loadSchools();
+ if (!isset(self::$schools[$schoolName]) || !is_array(self::$schools[$schoolName])) {
+ return PEAR::raiseError(sprintf(_("Error loading the school \"%s\" from template."), $schoolName));
+ } else {
+ $this->school = self::$schools[$schoolName];
+ }
+ }
+
+ /**
+ * Adds a variable to the current form.
+ *
+ * @param Horde_Form $form The current form.
+ *
+ * @param string $property The property to add.
+ *
+ * @param array $params Property dependent parameters.
+ */
+ function addFormVariable(&$form, $property, $params = array())
+ {
+ switch ($property) {
+ case 'start':
+ case 'end':
+ $form->addVariable(_(ucfirst($property)), $property, 'monthdayyear', true, false, null, array());
+ if ($form->_vars->exists('semester') && isset($this->school['semester']) && is_array($this->school['semester'])) {
+ foreach ($this->school['semester'] as $semester) {
+ if ($semester['name'] == $form->_vars->get('semester')) {
+ $activesemester = $semester;
+ break;
+ }
+ }
+ if (isset($activesemester[$property])) {
+ require_once 'Horde/Date.php';
+ if ($property == 'start') {
+ $startdate = 0;
+ } else {
+ $startdate = new Horde_Date($form->_vars->get('start'));
+ $startdate = $startdate->datestamp();
+ }
+ $date = new Horde_Date($this->_getSemesterTime($activesemester[$property], $startdate));
+ $form->_vars->set($property, array('month' => $date->month, 'day' => $date->mday, 'year' => $date->year));
+ }
+ }
+ break;
+
+ case 'marks':
+ $marksformat = array(
+ 'numbers' => _("Format in numbers"),
+ 'percent' => _("Format in percent"),
+ 'custom' => _("Custom format:")
+ );
+ if (isset($this->school[$property])) {
+ $form->_vars->set($property, $this->school[$property]);
+ if (!isset($marksformat[$this->school[$property]])) {
+ $marksformat['custom'] .= ' ' . $this->school[$property];
+ $marksformat[$this->school[$property]] = $marksformat['custom'];
+ unset($marksformat['custom']);
+ }
+ $form->addVariable(_(ucfirst($property)), $property, 'enum', true, true, null, array($marksformat, _("Choose:")));
+ } else {
+ require_once 'Horde/Form/Action.php';
+ if ($form->_vars->exists($property) && !isset($marksformat[$form->_vars->get($property)])) {
+ $form->_vars->set($property . '_custom', $form->_vars->get($property));
+ $form->_vars->set($property, 'custom');
+ }
+ $actionvariable = &$form->addVariable(_(ucfirst($property)), $property, 'enum', true, false, null, array($marksformat, _("Choose:")));
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ if ($form->_vars->get($property) == 'custom') {
+ $form->addVariable('', $property . '_custom', 'text', true, false, _("List with custom marks separated by comma (best mark first)"));
+ }
+ }
+ break;
+
+ case 'subject':
+ $obligatory = isset($params[0]) ? $params[0] : true;
+ $onlywithobjectives = isset($params[1]) ? $params[1] : false;
+ if (isset($this->school['subjects'])) {
+ $values = array();
+ foreach ($this->school['subjects'] as $key=>$value) {
+ if (!$onlywithobjectives || ($onlywithobjectives && is_array($value))) {
+ $subject = is_array($value) ? $key : $value;
+ $values[$subject] = $subject;
+ }
+ }
+ if ($onlywithobjectives) {
+ if (count($values) > 0) {
+ require_once 'Horde/Form/Action.php';
+ $actionvariable = &$form->addVariable(_(ucfirst($property)), 'attribute_subject', 'enum', $obligatory, false, null, array(array_merge(array(_("Interdisciplinary")=>_("Interdisciplinary")), $values)));
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ } else {
+ $form->addVariable(_(ucfirst($property)), 'attribute_subject', 'text', true, true);
+ $form->_vars->set('attribute_subject', _("Interdisciplinary"));
+ }
+ } else {
+ $form->addVariable(_(ucfirst($property)), 'attribute_subject', 'enum', $obligatory, false, null, array($values, _("Choose:")));
+ }
+ } else {
+ $form->addVariable(_(ucfirst($property)), $property, 'text', $obligatory, false);
+ }
+ break;
+
+ case 'category':
+ $subject = !empty($params[0]) ? $params[0] : _("Interdisciplinary");
+ if ($subject != _("Interdisciplinary") && isset($this->school['subjects'][$subject]) && is_array($this->school['subjects'][$subject])) {
+ $values = array();
+ foreach ($this->school['subjects'][$subject] as $value) {
+ $values[$value] = $value;
+ }
+ $form->addVariable(_(ucfirst($property)), 'attribute_category', 'enum', true, false, null, array($values, _("Choose:")));
+ } else if ($subject == _("Interdisciplinary") && isset($this->school['objectives'])) {
+ $values = array();
+ foreach ($this->school['objectives'] as $value) {
+ $values[$value] = $value;
+ }
+ $form->addVariable(_(ucfirst($property)), 'attribute_category', 'enum', true, false, null, array($values, _("Choose:")));
+ } else {
+ $form->addVariable(_(ucfirst($property)), 'attribute_category', 'text', true, false);
+ }
+ break;
+
+ default:
+ if (isset($this->school[$property]) && is_array($this->school[$property])) {
+ if (count($this->school[$property]) > 1) {
+ $values = array();
+ foreach ($this->school[$property] as $value) {
+ $key = is_array($value) ? $value['name'] : $value;
+ $values[$key] = $key;
+ }
+ if (is_array(current($this->school[$property]))) {
+ require_once 'Horde/Form/Action.php';
+ $actionvariable = &$form->addVariable(_(ucfirst($property)), $property, 'enum', false, false, null, array($values, _("Choose:")));
+ $actionvariable->setAction(Horde_Form_Action::factory('reload'));
+ } else {
+ $form->addVariable(_(ucfirst($property)), $property, 'enum', false, false, null, array($values, _("Choose:")));
+ }
+ } else {
+ $form->addVariable(_(ucfirst($property)), $property, 'text', false, true);
+ $value = current($this->school[$property]);
+ $form->_vars->set($property, is_array($value) ? $value['name'] : $value);
+ }
+ } else {
+ $form->addVariable(_(ucfirst($property)), $property, 'text', false, false);
+ }
+ }
+ }
+
+ /**
+ * Returns a timestamp for the specified semester start- or enddate.
+ *
+ * @param mixed The dateformat specified in conf/schools.php for this date.
+ *
+ * @return int The timestamp.
+ */
+ private function _getSemesterTime($format, $startdate)
+ {
+ if (is_int($format)) {
+ // Timestamp format
+ $timestamp = $format;
+ } else if (preg_match('/^W([0-9]{2})\-[0-9]$/', $format, $m)) {
+ $year = date('Y');
+ if (date('W') > $m[1]) {
+ $year++;
+ }
+ $timestamp = strtotime($year . '-' . $format);
+ } else {
+ $timestamp = strtotime($format);
+ }
+ if (is_int($timestamp) && $timestamp > 0) {
+ if ($startdate >= $timestamp) {
+ $timestamp = strtotime('+1 year', $timestamp);
+ }
+ return $timestamp;
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns all schools specified in conf/schools.php.
+ *
+ * @return array The school list.
+ */
+ public static function listSchools()
+ {
+ self::_loadSchools();
+ $schools = array();
+ foreach (self::$schools as $key=>$val) {
+ $schools[$key] = $val['title'];
+ }
+ return $schools;
+ }
+
+ /**
+ * Loads the schools specified in conf/schools.php
+ */
+ private static function _loadSchools()
+ {
+ if (!isset(self::$schools)) {
+ require_once SKOLI_BASE . '/config/schools.php';
+ self::$schools = $cfgSchools;
+ }
+ }
+
+ /**
+ * Returns all addressbooks skoli is defined to use.
+ *
+ * @param boolean $all If set to true return all addressbooks a user has access to.
+ *
+ * @return array The address book list.
+ */
+ public static function listAddressBooks($all = false)
+ {
+ global $conf, $prefs, $registry;
+
+ $addressbooks = $registry->call('contacts/sources');
+
+ if (!$all && $conf['addresses']['storage'] == 'custom') {
+ if (isset($addressbooks[$conf['addresses']['address_book']])) {
+ $addressbooks = array($conf['addresses']['address_book'] => $addressbooks[$conf['addresses']['address_book']]);
+ } else {
+ $addressbooks = array();
+ }
+ }
+
+ return $addressbooks;
+ }
+
+ /**
+ * Returns the parsed contact list name.
+ *
+ * @param string $contactlist The contact list name to parse.
+ *
+ * @param Horde_Variables $vars The variables to use as replacement.
+ *
+ * @param boolean $force If set to true also replaces empty fields.
+ *
+ * @return string The parsed contact list name.
+ */
+ public static function parseContactListName($contactlist, $vars, $force = false)
+ {
+ $contactlistsubs = array(
+ '%c' => 'name',
+ '%g' => 'grade',
+ '%s' => 'semester'
+ );
+ foreach ($contactlistsubs as $pattern=>$field) {
+ if (strpos($contactlist, $pattern) !== false && ($vars->get($field) != '' || $force)) {
+ $contactlist = str_replace($pattern, $vars->get($field), $contactlist);
+ }
+ }
+ return $contactlist;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Sort by student or class name.
+ */
+define('SKOLI_SORT_NAME', 'name');
+
+/**
+ * Sort by last entry date.
+ */
+define('SKOLI_SORT_LASTENTRY', 'lastentry');
+
+/**
+ * Sort by mark average.
+ */
+define('SKOLI_SORT_SUMMARKS', 'summarks');
+
+/**
+ * Sort by absences.
+ */
+define('SKOLI_SORT_SUMABSENCES', 'sumabsences');
+
+/**
+ * Sort by semester start date.
+ */
+define('SKOLI_SORT_SEMESTERSTART', 'semesterstart');
+
+/**
+ * Sort by semester end date.
+ */
+define('SKOLI_SORT_SEMESTEREND', 'semesterend');
+
+/**
+ * Sort by grade.
+ */
+define('SKOLI_SORT_GRADE', 'grade');
+
+/**
+ * Sort by semester.
+ */
+define('SKOLI_SORT_SEMESTER', 'semester');
+
+/**
+ * Sort by location.
+ */
+define('SKOLI_SORT_LOCATION', 'location');
+
+/**
+ * Sort by category.
+ */
+define('SKOLI_SORT_CATEGORY', 'category');
+
+/**
+ * Sort by entry class name.
+ */
+define('SKOLI_SORT_CLASS', 'class');
+
+/**
+ * Sort by entry student name.
+ */
+define('SKOLI_SORT_STUDENT', 'student');
+
+/**
+ * Sort by entry date.
+ */
+define('SKOLI_SORT_DATE', 'date');
+
+/**
+ * Sort by entry type.
+ */
+define('SKOLI_SORT_TYPE', 'type');
+
+/**
+ * Sort in ascending order.
+ */
+define('SKOLI_SORT_ASCEND', 0);
+
+/**
+ * Sort in descending order.
+ */
+define('SKOLI_SORT_DESCEND', 1);
+
+/**
+ * Skoli Base Class.
+ *
+ * $Horde: skoli/lib/Skoli.php,v 0.1 $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ * @package Skoli
+ */
+class Skoli {
+
+ /**
+ * Initial app setup code.
+ */
+ function initialize()
+ {
+ // Update the preference for what classes to display. If the user
+ // doesn't have any selected class then do nothing.
+ $GLOBALS['display_classes'] = @unserialize($GLOBALS['prefs']->getValue('display_classes'));
+ if (!$GLOBALS['display_classes']) {
+ $GLOBALS['display_classes'] = array();
+ }
+ if (($classId = Util::getFormData('display_class')) !== null) {
+ if (is_array($classId)) {
+ $GLOBALS['display_classes'] = $classId;
+ } else {
+ if (in_array($classId, $GLOBALS['display_classes'])) {
+ $key = array_search($classId, $GLOBALS['display_classes']);
+ unset($GLOBALS['display_classes'][$key]);
+ } else {
+ $GLOBALS['display_classes'][] = $classId;
+ }
+ }
+ $GLOBALS['prefs']->setValue('show_students', Util::getFormData('show_students') ? 1 : 0);
+ }
+
+ $GLOBALS['prefs']->setValue('display_classes', serialize($GLOBALS['display_classes']));
+ }
+
+ /**
+ * Returns all classes a user has access to, according to several
+ * parameters/permission levels.
+ *
+ * @param boolean $owneronly Only return classes that this user owns?
+ * Defaults to false.
+ * @param integer $permission The permission to filter classes by.
+ *
+ * @return array The class list.
+ */
+ function listClasses($owneronly = false, $permission = PERMS_SHOW)
+ {
+ global $registry;
+
+ $classes = $GLOBALS['skoli_shares']->listShares(Auth::getAuth(), $permission, $owneronly ? Auth::getAuth() : null, 0, 0, 'name');
+ if (is_a($classes, 'PEAR_Error')) {
+ Horde::logMessage($classes, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return array();
+ }
+
+ // Check if we have access to the attached addressbook.
+ $addressbooks = $registry->call('contacts/sources');
+ foreach ($classes as $key=>$val) {
+ if (!isset($addressbooks[$val->get('address_book')])) {
+ unset($classes[$key]);
+ }
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Retrieves the current user's student list from storage.
+ *
+ * This function will also sort the resulting list, if requested.
+ *
+ * @param array $classes An array of classes to display, a
+ * single classname or null/empty to
+ * display classes $GLOBALS['display_classes'].
+ * @param string $sortby_student The field by which to sort
+ * (SKOLI_SORT_PRIORITY, SKOLI_SORT_NAME
+ * SKOLI_SORT_DUE, SKOLI_SORT_COMPLETION).
+ * @param integer $sortdir_student The direction by which to sort
+ * (SKOLI_SORT_ASCEND, SKOLI_SORT_DESCEND).
+ * @param string $sortby_class The field by which to sort
+ * (SKOLI_SORT_PRIORITY, SKOLI_SORT_NAME
+ * SKOLI_SORT_DUE, SKOLI_SORT_COMPLETION).
+ * @param integer $sortdir_class The direction by which to sort
+ * (SKOLI_SORT_ASCEND, SKOLI_SORT_DESCEND).
+ *
+ * @return array A list of the requested classes with students.
+ */
+ function listStudents($classes = null,
+ $sortby_student = null,
+ $sortdir_student = null,
+ $sortby_class = null,
+ $sortdir_class = null)
+ {
+ global $prefs, $registry;
+
+ if (is_null($classes)) {
+ $classes = $GLOBALS['display_classes'];
+ } else if (!is_array($classes)) {
+ $classes = array($classes);
+ }
+ if (is_null($sortby_student)) {
+ $sortby_student = $prefs->getValue('sortby_student');
+ }
+ if (is_null($sortdir_student)) {
+ $sortdir_student = $prefs->getValue('sortdir_student');
+ }
+ if (is_null($sortby_class)) {
+ $sortby_class = $prefs->getValue('sortby_class');
+ }
+ if (is_null($sortdir_class)) {
+ $sortdir_class = $prefs->getValue('sortdir_class');
+ }
+
+ $list = array();
+ $i = 0;
+ $addressbooks = $registry->call('contacts/sources');
+ foreach ($classes as $class) {
+ /* Get all data about the shared class */
+ $share = $GLOBALS['skoli_shares']->getShare($class);
+
+ /* Check permissions */
+ if (!$share->hasPermission(Auth::getAuth(), PERMS_READ) || !isset($addressbooks[$share->get('address_book')])) {
+ continue;
+ }
+
+ $list[$i] = $share->datatreeObject->data;
+ $list[$i]['_id'] = $class;
+ $list[$i]['_edit'] = $share->hasPermission(Auth::getAuth(), PERMS_EDIT);
+
+ /* Add all students to the list */
+ $driver = &Skoli_Driver::singleton($class);
+ $list[$i]['_students'] = $driver->getStudents();
+ $student_columns = @unserialize($prefs->getValue('student_columns'));
+ foreach ($list[$i]['_students'] as $key=>$student) {
+ $studentdetails = Skoli::getStudent($list[$i]['address_book'], $student['student_id']);
+ if (count($studentdetails) > 0) {
+ $list[$i]['_students'][$key] += $studentdetails;
+ if (in_array('lastentry', $student_columns)) {
+ $list[$i]['_students'][$key]['_lastentry'] = $driver->lastEntry($student['student_id']);
+ }
+ if (in_array('summarks', $student_columns)) {
+ $list[$i]['_students'][$key]['_summarks'] = Skoli::sumMarks($class, $student['student_id']);
+ }
+ if (in_array('sumabsences', $student_columns)) {
+ $list[$i]['_students'][$key]['_sumabsences'] = Skoli::sumAbsences($class, $student['student_id']);
+ }
+ } else {
+ unset($list[$i]['_students'][$key]);
+ }
+ }
+ $i++;
+ }
+
+ /* Sort the array if we have a sort function defined for this
+ * field. */
+ $prefix = ($sortdir_class == SKOLI_SORT_DESCEND) ? '_rsort' : '_sort';
+ usort($list, array('Skoli', $prefix . '_class_' . $sortby_class));
+ $prefix = ($sortdir_student == SKOLI_SORT_DESCEND) ? '_rsort' : '_sort';
+ for ($i = 0; $i < count($list); $i++) {
+ usort($list[$i]['_students'], array('Skoli', $prefix . '_student_' . $sortby_student));
+ }
+
+ return $list;
+ }
+
+ /**
+ * Retrieves all data about a student.
+ *
+ * @param string $addressbook The addressbook.
+ *
+ * @param string $id An ID from the student contact.
+ *
+ * @return array A list with the data from the requested student.
+ */
+ function getStudent($addressbook, $id)
+ {
+ global $registry;
+
+ $student = array();
+ $apiargs = array(
+ 'source' => $addressbook,
+ 'objectId' => $id
+ );
+ $result = $registry->call('contacts/getContact', $apiargs);
+ if ($result === false || is_a($result, 'PEAR_Error')) {
+ $notification->push(sprintf(_("Couldn't create the contact list \"%s\"."), $this->_vars->get('contact_list')), 'horde.info');
+ } else {
+ $student = $result;
+ }
+ return $student;
+ }
+
+ /**
+ * Retrieves a sorted entry list from storage.
+ *
+ * @param string $classid The class ID.
+ *
+ * @param string $studentid The student ID.
+ *
+ * @param string $type The entry type to search in.
+ *
+ * @param array $searchparams Some additional search parameters.
+ *
+ * @param string $sortby The field by which to sort
+ * (SKOLI_SORT_CLASS, SKOLI_SORT_STUDENT
+ * SKOLI_SORT_DATE, SKOLI_SORT_TYPE).
+ * @param integer $sortdir The direction by which to sort
+ * (SKOLI_SORT_ASCEND, SKOLI_SORT_DESCEND).
+ *
+ * @return array Sorted list with all entries.
+ */
+ function listEntries($classid = null,
+ $studentid = null,
+ $type = null,
+ $searchparams = array(),
+ $sortby = null,
+ $sortdir = null)
+ {
+ global $conf, $prefs, $registry;
+
+ $dateFormat = $prefs->getValue('date_format');
+ $entryTypes = array(
+ 'mark' => _("Mark"),
+ 'objective' => _("Objective"),
+ 'outcome' => _("Outcome"),
+ 'absence' => _("Absence")
+ );
+
+ if (is_null($classid)) {
+ $classes = Skoli::listClasses();
+ } else {
+ $share = $GLOBALS['skoli_shares']->getShare($classid);
+ $classes = array($classid => $share);
+ }
+
+ if (is_null($sortby)) {
+ $sortby = SKOLI_SORT_CLASS;
+ }
+ if (is_null($sortdir)) {
+ $sortdir = SKOLI_SORT_ASCEND;
+ }
+
+ $entrylist = array();
+ $i = 0;
+ $addressbooks = $registry->call('contacts/sources');
+ foreach ($classes as $class_id=>$share) {
+ /* Check permissions */
+ if (!$share->hasPermission(Auth::getAuth(), PERMS_READ) || !isset($addressbooks[$share->get('address_book')])) {
+ continue;
+ }
+
+ $share_permissions_edit = $share->hasPermission(Auth::getAuth(), PERMS_EDIT);
+ $share_permissions_delete = $share->hasPermission(Auth::getAuth(), PERMS_DELETE);
+
+ $driver = &Skoli_Driver::singleton($class_id);
+ $entries = $driver->getEntries($studentid, $type, $searchparams);
+ foreach ($entries as $student) {
+ $studentdetails = Skoli::getStudent($share->get('address_book'), $student['student_id']);
+ foreach ($student['_entries'] as $entry) {
+ $entrylist[$i] = $entry['_attributes'];
+ $entrylist[$i]['class'] = $share->get('name');
+ $entrylist[$i]['classid'] = $class_id;
+ $entrylist[$i]['student'] = $studentdetails[$conf['addresses']['name_field']];
+ $entrylist[$i]['date'] = strftime($dateFormat, $entry['object_time']);
+ $entrylist[$i]['timestamp'] = $entry['object_time'];
+ $entrylist[$i]['typename'] = $entryTypes[$entry['object_type']];
+ $entrylist[$i]['type'] = $entry['object_type'];
+ $entrylist[$i]['_edit'] = $share_permissions_edit;
+ $entrylist[$i]['_delete'] = $share_permissions_delete;
+ $entrylist[$i]['_id'] = $entry['object_id'];
+ $i++;
+ }
+ }
+ }
+
+ /* Sort the array if we have a sort function defined for this
+ * field. */
+ $prefix = ($sortdir == SKOLI_SORT_DESCEND) ? '_rsort' : '_sort';
+ usort($entrylist, array('Skoli', $prefix . '_entry_' . $sortby));
+
+ return $entrylist;
+ }
+
+ /**
+ * Sum up all excused and not excused absences for a given student.
+ *
+ * @param string $classid An ID from the class.
+ *
+ * @param string $studentid An ID from the student contact.
+ *
+ * @return array A list with the requested data.
+ */
+ function sumAbsences($classid, $studentid)
+ {
+ $driver = &Skoli_Driver::singleton($classid);
+ $entries = current($driver->getEntries($studentid, 'absence'));
+
+ $excused = 0;
+ $notexcused = 0;
+ foreach($entries['_entries'] as $entry) {
+ $entry['_attributes']['absence'] = Skoli::convertNumber($entry['_attributes']['absence']);
+ if (empty($entry['_attributes']['excused'])) {
+ $notexcused += $entry['_attributes']['absence'];
+ } else {
+ $excused += $entry['_attributes']['absence'];
+ }
+ }
+
+ return array($excused, $notexcused, $excused + $notexcused);
+ }
+
+ /**
+ * Sum up all completed and open outcomes for a given student.
+ *
+ * @param string $classid An ID from the class.
+ *
+ * @param string $studentid An ID from the student contact.
+ *
+ * @return array A list with the requested data.
+ */
+ function sumOutcomes($classid, $studentid)
+ {
+ $driver = &Skoli_Driver::singleton($classid);
+ $entries = current($driver->getEntries($studentid, 'outcome'));
+
+ $completed = 0;
+ $open = 0;
+ foreach($entries['_entries'] as $entry) {
+ if (empty($entry['_attributes']['completed'])) {
+ $open++;
+ } else {
+ $completed++;
+ }
+ }
+
+ return array($completed, $open, $completed + $open);
+ }
+
+ /**
+ * Sum up all marks for a given student.
+ *
+ * @param string $classid An ID from the class.
+ *
+ * @param string $studentid An ID from the student contact.
+ *
+ * @param string $subject Only sum up marks from this subject.
+ *
+ * @return float The requested data.
+ */
+ function sumMarks($classid, $studentid, $subject = null)
+ {
+ global $prefs;
+
+ $driver = &Skoli_Driver::singleton($classid);
+ if (!is_null($subject)) {
+ $params = array(array('name' => 'subject', 'value' => $subject, 'strict' => 1));
+ } else {
+ $params = null;
+ }
+ $entries = current($driver->getEntries($studentid, 'mark', $params));
+
+ /* Count weights */
+ $totalweight = 0;
+ foreach($entries['_entries'] as $entry) {
+ $totalweight += Skoli::convertNumber($entry['_attributes']['weight']);
+ }
+
+ if ($totalweight <= 0) {
+ return '';
+ }
+
+ $sum = 0;
+ $weight = 100 / $totalweight;
+ foreach($entries['_entries'] as $entry) {
+ $sum += $weight * Skoli::convertNumber($entry['_attributes']['weight']) * Skoli::convertNumber($entry['_attributes']['mark']);
+ }
+
+ if ($sum > 0) {
+ return round($sum / 100, $prefs->getValue('marks_roundby'));
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Converts numbers with a comma to a valid php number.
+ *
+ * @param string $number The number to convert.
+ *
+ * @return string The converted number
+ */
+ function convertNumber($number)
+ {
+ $number = str_replace(',', '.', $number);
+ return $number;
+ }
+
+ /**
+ * Build Skoli's list of menu items.
+ */
+ function getMenu($returnType = 'object')
+ {
+ global $conf, $registry, $browser, $print_link;
+
+ require_once 'Horde/Menu.php';
+
+ $menu = new Menu(HORDE_MENU_MASK_ALL);
+ $menu->add(Horde::applicationUrl('list.php'), _("List Classes"), 'skoli.png', null, null, null, basename($_SERVER['PHP_SELF']) == 'index.php' ? 'current' : null);
+ if (count(Skoli::listClasses(false, PERMS_EDIT))) {
+ $menu->add(Horde::applicationUrl('add.php'), _("_New Entry"), 'add.png', null, null, null, Util::getFormData('entry') ? '__noselection' : null);
+ }
+
+ /* Search. */
+ $menu->add(Horde::applicationUrl('search.php'), _("_Search"), 'search.png', $registry->getImageDir('horde'));
+
+ /* Import/Export. */
+ if ($conf['menu']['export']) {
+ $menu->add(Horde::applicationUrl('data.php'), _("_Export"), 'data.png', $registry->getImageDir('horde'));
+ }
+
+ // @TODO Implement an easy form to create timetables in e.g. Kronolith
+ /* Timetable.
+ * Show this item only if an application provides 'calendar/show' and we have permissions to view it.
+ $app = $registry->hasMethod('calendar/show');
+ if ($app !== false && $registry->get('status', $app) != 'inactive' && $registry->hasPermission($app, PERMS_EDIT)) {
+ $menu->add(Horde::applicationUrl(Util::addParameter('timetable.php', 'actionID', 'new_timetable')), _("_New Timetable"), 'timetable.png');
+ }
+ */
+
+ if ($returnType == 'object') {
+ return $menu;
+ } else {
+ return $menu->render();
+ }
+ }
+
+ /**
+ * Comparison function for sorting classes by semester start date.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_semesterstart($a, $b)
+ {
+ if ($a['start'] == $b['start'] ) {
+ return 0;
+ }
+
+ // Treat empty start dates as farthest into the future.
+ if ($a['start'] == 0) {
+ return 1;
+ }
+ if ($b['start'] == 0) {
+ return -1;
+ }
+
+ return ($a['start'] > $b['start']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by semester start date.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_class_semesterstart($a, $b)
+ {
+ if ($a['start'] == $b['start']) {
+ return 0;
+ }
+
+ // Treat empty start dates as farthest into the future.
+ if ($a['start'] == 0) {
+ return -1;
+ }
+ if ($b['start'] == 0) {
+ return 1;
+ }
+
+ return ($a['start'] < $b['start']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting classes by semester end date.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_semesterend($a, $b)
+ {
+ if ($a['end'] == $b['end'] ) {
+ return 0;
+ }
+
+ // Treat empty end dates as farthest into the future.
+ if ($a['end'] == 0) {
+ return 1;
+ }
+ if ($b['end'] == 0) {
+ return -1;
+ }
+
+ return ($a['end'] > $b['end']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by semester end date.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_class_semesterend($a, $b)
+ {
+ if ($a['end'] == $b['end']) {
+ return 0;
+ }
+
+ // Treat empty end dates as farthest into the future.
+ if ($a['end'] == 0) {
+ return -1;
+ }
+ if ($b['end'] == 0) {
+ return 1;
+ }
+
+ return ($a['end'] < $b['end']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting classes by name.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_name($a, $b)
+ {
+ return strcasecmp($a['name'], $b['name']);
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by name.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _rsort_class_name($a, $b)
+ {
+ return strcasecmp($b['name'], $a['name']);
+ }
+
+ /**
+ * Comparison function for sorting classes by grade.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_grade($a, $b)
+ {
+ return strcasecmp($a['grade'], $b['grade']);
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by grade.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _rsort_class_grade($a, $b)
+ {
+ return strcasecmp($b['grade'], $a['grade']);
+ }
+
+ /**
+ * Comparison function for sorting classes by semester.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_semester($a, $b)
+ {
+ return strcasecmp($a['semester'], $b['semester']);
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by semester.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _rsort_class_semester($a, $b)
+ {
+ return strcasecmp($b['semester'], $a['semester']);
+ }
+
+ /**
+ * Comparison function for sorting classes by location.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_location($a, $b)
+ {
+ return strcasecmp($a['location'], $b['location']);
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by location.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _rsort_class_location($a, $b)
+ {
+ return strcasecmp($b['location'], $a['location']);
+ }
+
+ /**
+ * Comparison function for sorting classes by category.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer 1 if class one is greater, -1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_class_category($a, $b)
+ {
+ return strcasecmp($a['category'] ? $a['category'] : _("Unfiled"),
+ $b['category'] ? $b['category'] : _("Unfiled"));
+ }
+
+ /**
+ * Comparison function for reverse sorting classes by category.
+ *
+ * @param array $a Class one.
+ * @param array $b Class two.
+ *
+ * @return integer -1 if class one is greater, 1 if class two is greater;
+ * 0 if they are equal.
+ */
+ function _rsort_class_category($a, $b)
+ {
+ return strcasecmp($b['category'] ? $b['category'] : _("Unfiled"),
+ $a['category'] ? $a['category'] : _("Unfiled"));
+ }
+
+ /**
+ * Comparison function for sorting students by name.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer 1 if student one is greater, -1 if student two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_student_name($a, $b)
+ {
+ return strcasecmp($a['name'], $b['name']);
+ }
+
+ /**
+ * Comparison function for reverse sorting students by name.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer -1 if student one is greater, 1 if student two is greater;
+ * 0 if they are equal.
+ */
+ function _rsort_student_name($a, $b)
+ {
+ return strcasecmp($b['name'], $a['name']);
+ }
+
+ /**
+ * Comparison function for sorting students by last entry date.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer 1 if student one is greater, -1 if student two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_student_lastentry($a, $b)
+ {
+ // Treat empty dates as farthest into the past.
+ if (!isset($a['_lastentry']) || $a['_lastentry'] == 0) {
+ return -1;
+ }
+ if (!isset($b['_lastentry']) || $b['_lastentry'] == 0) {
+ return 1;
+ }
+
+ if ($a['_lastentry'] == $b['_lastentry'] ) {
+ return 0;
+ }
+
+ return ($a['_lastentry'] > $b['_lastentry']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting students by last entry date.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer -1 if student one is greater, 1 if student two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_student_lastentry($a, $b)
+ {
+ // Treat empty dates as farthest into the past.
+ if (!isset($a['_lastentry']) || $a['_lastentry'] == 0) {
+ return 1;
+ }
+ if (!isset($b['_lastentry']) || $b['_lastentry'] == 0) {
+ return -1;
+ }
+
+ if ($a['_lastentry'] == $b['_lastentry']) {
+ return 0;
+ }
+
+ return ($a['_lastentry'] < $b['_lastentry']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting students by sumabsences.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer 1 if student one is greater, -1 if student two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_student_sumabsences($a, $b)
+ {
+ if ($a['_sumabsences'] == $b['_sumabsences'] ) {
+ return 0;
+ }
+
+ return ($a['_sumabsences'] > $b['_sumabsences']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting students by sumabsences.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer -1 if student one is greater, 1 if student two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_student_sumabsences($a, $b)
+ {
+ if ($a['_sumabsences'] == $b['_sumabsences']) {
+ return 0;
+ }
+
+ return ($a['_sumabsences'] < $b['_sumabsences']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting students by summarks.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer 1 if student one is greater, -1 if student two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_student_summarks($a, $b)
+ {
+ // Treat empty sums as lowest mark.
+ if ($a['_summarks'] == '') {
+ return -1;
+ }
+ if ($b['_summarks'] == '') {
+ return 1;
+ }
+
+ if ($a['_summarks'] == $b['_summarks'] ) {
+ return 0;
+ }
+
+ return ($a['_summarks'] > $b['_summarks']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting students by summarks.
+ *
+ * @param array $a Student one.
+ * @param array $b Student two.
+ *
+ * @return integer -1 if student one is greater, 1 if student two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_student_summarks($a, $b)
+ {
+ // Treat empty sums as lowest mark.
+ if ($a['_summarks'] == '') {
+ return 1;
+ }
+ if ($b['_summarks'] == '') {
+ return -1;
+ }
+
+ if ($a['_summarks'] == $b['_summarks']) {
+ return 0;
+ }
+
+ return ($a['_summarks'] < $b['_summarks']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting entries by date.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer 1 if entry one is greater, -1 if entry two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_entry_date($a, $b)
+ {
+ if ($a['timestamp'] == $b['timestamp'] ) {
+ return 0;
+ }
+
+ return ($a['timestamp'] > $b['timestamp']) ? -1 : 1;
+ }
+
+ /**
+ * Comparison function for reverse sorting entries by date.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer -1 if entry one is greater, 1 if entry two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_entry_date($a, $b)
+ {
+ if ($a['timestamp'] == $b['timestamp']) {
+ return 0;
+ }
+
+ return ($a['timestamp'] < $b['timestamp']) ? -1 : 1;
+ }
+
+ /**
+ * Comparison function for sorting entries by class.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer 1 if entry one is greater, -1 if entry two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_entry_class($a, $b)
+ {
+ if ($a['class'] == $b['class'] ) {
+ return Skoli::_sort_entry_date($a, $b);
+ }
+
+ return ($a['class'] > $b['class']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting entries by class.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer -1 if entry one is greater, 1 if entry two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_entry_class($a, $b)
+ {
+ if ($a['class'] == $b['class']) {
+ return Skoli::_rsort_entry_date($a, $b);
+ }
+
+ return ($a['class'] < $b['class']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting entries by student.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer 1 if entry one is greater, -1 if entry two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_entry_student($a, $b)
+ {
+ if ($a['student'] == $b['student'] ) {
+ return Skoli::_sort_entry_date($a, $b);
+ }
+
+ return ($a['student'] > $b['student']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting entries by student.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer -1 if entry one is greater, 1 if entry two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_entry_student($a, $b)
+ {
+ if ($a['student'] == $b['student']) {
+ return Skoli::_rsort_entry_date($a, $b);
+ }
+
+ return ($a['student'] < $b['student']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for sorting entries by type.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer 1 if entry one is greater, -1 if entry two is greater;
+ * 0 if they are equal.
+ */
+ function _sort_entry_type($a, $b)
+ {
+ if ($a['type'] == $b['type'] ) {
+ return Skoli::_sort_entry_date($a, $b);
+ }
+
+ return ($a['type'] > $b['type']) ? 1 : -1;
+ }
+
+ /**
+ * Comparison function for reverse sorting entries by type.
+ *
+ * @param array $a Entry one.
+ * @param array $b Entry two.
+ *
+ * @return integer -1 if entry one is greater, 1 if entry two is greater,
+ * 0 if they are equal.
+ */
+ function _rsort_entry_type($a, $b)
+ {
+ if ($a['type'] == $b['type']) {
+ return Skoli::_rsort_entry_date($a, $b);
+ }
+
+ return ($a['type'] < $b['type']) ? 1 : -1;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Skoli base application file.
+ *
+ * $Horde: skoli/lib/base.php,v 0.1 $
+ *
+ * This file brings in all of the dependencies that every Skoli script will
+ * need, and sets up objects that all scripts use.
+ */
+
+// Check for a prior definition of HORDE_BASE (perhaps by an auto_prepend_file
+// definition for site customization).
+if (!defined('HORDE_BASE')) {
+ @define('HORDE_BASE', dirname(__FILE__) . '/../..');
+}
+
+// Load the Horde Framework core, and set up inclusion paths.
+require_once HORDE_BASE . '/lib/core.php';
+
+// Registry.
+$registry = &Registry::singleton();
+if (is_a(($pushed = $registry->pushApp('skoli', !defined('AUTH_HANDLER'))), 'PEAR_Error')) {
+ if ($pushed->getCode() == 'permission_denied') {
+ Horde::authenticationFailureRedirect();
+ }
+ Horde::fatal($pushed, __FILE__, __LINE__, false);
+}
+$conf = &$GLOBALS['conf'];
+@define('SKOLI_TEMPLATES', $registry->get('templates'));
+
+// Horde framework libraries.
+require_once 'Horde/History.php';
+
+// Notification system.
+$notification = &Notification::singleton();
+$notification->attach('status');
+
+// Define the base file path of Skoli.
+@define('SKOLI_BASE', dirname(__FILE__) . '/..');
+
+// Skoli base library
+require_once SKOLI_BASE . '/lib/Skoli.php';
+require_once SKOLI_BASE . '/lib/Driver.php';
+
+// Start output compression.
+Horde::compressOutput();
+
+// Create a share instance.
+require_once 'Horde/Share.php';
+$GLOBALS['skoli_shares'] = &Horde_Share::singleton($registry->getApp());
+
+Skoli::initialize();
--- /dev/null
+<?php define('SKOLI_VERSION', '0.1-cvs') ?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/list.php,v 0.1 $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ */
+
+@define('SKOLI_BASE', dirname(__FILE__));
+require_once SKOLI_BASE . '/lib/base.php';
+
+$title = _("My Classes");
+
+/* Get and set Variables */
+require_once 'Horde/Variables.php';
+$vars = Variables::getDefaultVariables();
+
+/* Get the current action ID. */
+$actionID = Util::getFormData('actionID');
+
+/* Sort out the sorting values */
+if (($sortby_class = Util::getFormData('sortby_class')) !== null) {
+ if ($sortby_class == $prefs->getValue('sortby_class')) {
+ $prefs->setValue('sortdir_class', !$prefs->getValue('sortdir_class'));
+ } else {
+ $prefs->setValue('sortdir_class', SKOLI_SORT_ASCEND);
+ }
+ $prefs->setValue('sortby_class', $sortby_class);
+ if ($sortby_class == 'name') {
+ if ($sortby_class == $prefs->getValue('sortby_student')) {
+ $prefs->setValue('sortdir_student', !$prefs->getValue('sortdir_student'));
+ } else {
+ $prefs->setValue('sortdir_student', SKOLI_SORT_ASCEND);
+ }
+ $prefs->setValue('sortby_student', $sortby_class);
+ }
+}
+if (($sortby_student = Util::getFormData('sortby_student')) !== null) {
+ $prefs->setValue('sortby_student', $sortby_student);
+ if ($sortby_student == $prefs->getValue('sortby_student')) {
+ $prefs->setValue('sortdir_student', !$prefs->getValue('sortdir_student'));
+ } else {
+ $prefs->setValue('sortdir_student', SKOLI_SORT_ASCEND);
+ }
+}
+
+/* Check if we have access to an application who provides contacts/getContact */
+$app = $registry->hasMethod('contacts/getContact');
+if ($app == false || $registry->get('status', $app) == 'inactive' || !$registry->hasPermission($app, PERMS_SHOW)) {
+ $notification->push(_("Skoli needs an applications who provides contacts (e.g. turba)."), 'horde.warning');
+}
+
+/* Redirect to create a new class if we don't have access to any class */
+if (count(Skoli::listClasses()) == 0 && Auth::getAuth()) {
+ $notification->push(_("Please create a new Class first."), 'horde.message');
+ header('Location: ' . Horde::applicationUrl('classes/create.php', true));
+ exit;
+}
+
+switch ($actionID) {
+case 'search_classes':
+ /* Get the search parameters. */
+ $search_pattern = Util::getFormData('search_pattern');
+
+ /* Get the full, sorted student list for all classes. */
+ $list = Skoli::listStudents(null,
+ $prefs->getValue('sortby_student'),
+ $prefs->getValue('sortdir_student'),
+ $prefs->getValue('sortby_class'),
+ $prefs->getValue('sortdir_class'));
+
+ if (!empty($search_pattern)) {
+ $pattern = '/' . preg_quote($search_pattern, '/') . '/i';
+ $search_results = array();
+ foreach ($list as $class) {
+ $search_results_students = array();
+ if (($search_name && preg_match($pattern, $task->name)) ||
+ ($search_desc && preg_match($pattern, $task->desc)) ||
+ ($search_category && preg_match($pattern, $task->category))) {
+ $search_results->add($task);
+ }
+ }
+
+ /* Reassign $list to the search result. */
+ $list = $search_results;
+ $title = sprintf(_("Search: Results for \"%s\""), $search_pattern);
+ }
+ break;
+
+default:
+ /* Get the full, sorted list for all classes. */
+ $list = Skoli::listStudents(null,
+ $prefs->getValue('sortby_student'),
+ $prefs->getValue('sortdir_student'),
+ $prefs->getValue('sortby_class'),
+ $prefs->getValue('sortdir_class'));
+ break;
+}
+
+Horde::addScriptFile('popup.js', 'horde', true);
+Horde::addScriptFile('tooltip.js', 'horde', true);
+Horde::addScriptFile('prototype.js', 'horde', true);
+Horde::addScriptFile('effects.js', 'horde', true);
+Horde::addScriptFile('QuickFinder.js', 'horde', true);
+
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+echo '<div id="page">';
+require SKOLI_TEMPLATES . '/list/header.inc';
+
+if (count($list) > 0) {
+ $sortby_class = $prefs->getValue('sortby_class');
+ $sortdir_class = $prefs->getValue('sortdir_class');
+ $sortby_student = $prefs->getValue('sortby_student');
+ $sortdir_student = $prefs->getValue('sortdir_student');
+ $dateFormat = $prefs->getValue('date_format');
+ $class_columns = @unserialize($prefs->getValue('class_columns'));
+ $show_students = $prefs->getValue('show_students');
+ $student_columns = $show_students ? @unserialize($prefs->getValue('student_columns')) : array();
+ $dynamic_sort = true;
+
+ $baseurl = 'list.php';
+ if ($actionID == 'search_classes') {
+ $baseurl = Util::addParameter(
+ $baseurl,
+ array('actionID' => 'search_classes',
+ 'search_pattern' => $search_pattern));
+ }
+
+ require SKOLI_TEMPLATES . '/list/headers.inc';
+
+ foreach ($list as $class) {
+ $dynamic_sort &= !$show_students;
+ $style = 'linedRow';
+ require SKOLI_TEMPLATES . '/list/classes.inc';
+
+ if ($show_students) {
+ $treedir = $registry->getImageDir('horde');
+ $counter = 0;
+ foreach ($class['_students'] as $student) {
+ if (++$counter < count($class['_students'])) {
+ $treeIcon = Horde::img(empty($GLOBALS['nls']['rtl'][$GLOBALS['language']]) ? 'tree/join.png' : 'tree/rev-join.png', '+', '', $treedir);
+ } else {
+ $treeIcon = Horde::img(empty($GLOBALS['nls']['rtl'][$GLOBALS['language']]) ? 'tree/joinbottom.png' : 'tree/rev-joinbottom.png', '\\', '', $treedir);
+ }
+ require SKOLI_TEMPLATES . '/list/students.inc';
+ }
+ }
+ }
+
+ require SKOLI_TEMPLATES . '/list/footers.inc';
+
+ if ($dynamic_sort) {
+ Horde::addScriptFile('tables.js', 'horde', true);
+ }
+} else {
+ require SKOLI_TEMPLATES . '/list/empty.inc';
+}
+
+require SKOLI_TEMPLATES . '/panel.inc';
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- $Horde: skoli/locale/de_DE/help.xml,v 0.1 $ -->
+<help>
+ <entry id="Overview">
+ <title>Skoli Übersicht</title>
+ <heading>Was ist Skoli?</heading>
+ <para>
+ Skoli ist ein einfaches Verwaltungsmodul für Lehrpersonen. Mehrere
+ Kontakte (Studierende) werden zu einer Klasse zusammengefasst. Zu
+ jedem Studierenden können anschliessend Noten, Beobachtungen, Lernziele
+ und Absenzen eingetragen werden. Neben einer Suchfunktion bietet Skoli
+ auch eine Exportmöglichkeit im CSV und TSV Format.
+ </para>
+ </entry>
+</help>
--- /dev/null
+<?xml version='1.0'?>
+<!-- $Horde: skoli/locale/en_US/help.xml,v 0.1 $ -->
+<help>
+
+<entry id="Overview">
+ <title>Skoli Overview</title>
+ <heading>What is Skoli?</heading>
+ <para>
+ Skoli is a simple administrative module for teachers. Several
+ contacts (students) will be summarized to a class. To each
+ student one can then enter marks, objectives, outcomes and
+ absences. Besides offering a search function Skoli also offers
+ an export option in the CSV and TSV formats.
+ </para>
+</entry>
+
+</help>
--- /dev/null
+see horde/po/README
--- /dev/null
+# German translations for Skoli package
+# German messages for Skoli.
+# Copyright (C) 2009 Horde Project
+# This file is distributed under the same license as the Skoli package.
+# Automatically generated, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Skoli 0.1-cvs\n"
+"Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+"POT-Creation-Date: 2009-04-16 11:04+0200\n"
+"PO-Revision-Date: 2009-04-16 11:04+0200\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: i18n@lists.horde.org\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: config/schools.php.dist:57
+msgid "1. class"
+msgstr "1. Klasse"
+
+#: config/schools.php.dist:66
+msgid "1. term"
+msgstr "1. Halbjahr"
+
+#: config/schools.php.dist:58
+msgid "2. class"
+msgstr "2. Klasse"
+
+#: config/schools.php.dist:71
+msgid "2. term"
+msgstr "2. Halbjahr"
+
+#: config/schools.php.dist:59
+msgid "3. class"
+msgstr "3. Klasse"
+
+#: config/schools.php.dist:60
+msgid "4. class"
+msgstr "4. Klasse"
+
+#: config/schools.php.dist:61
+msgid "5. class"
+msgstr "5. Klasse"
+
+#: config/schools.php.dist:62
+msgid "6. class"
+msgstr "6. Klasse"
+
+#: lib/Skoli.php:315 lib/Forms/Entry.php:106 lib/Forms/Entry.php:156
+msgid "Absence"
+msgstr "Abwesenheit"
+
+#: lib/Forms/Entry.php:156
+msgid "Absence in number of lessons"
+msgstr "Abwesenheit in Anzahl Lektionen"
+
+#: data.php:134 search.php:116 templates/list/headers.inc:68
+#: config/prefs.php.dist:94 config/prefs.php.dist:107
+msgid "Absences"
+msgstr "Abwesenheiten"
+
+#: data.php:73
+msgid "Absences without valid excuse"
+msgstr "Unentschuldigte Abwesenheiten"
+
+#: lib/Driver.php:68
+msgid "Access for class \"%s\" is denied"
+msgstr "Zugriff auf die Klasse \"%s\" wurde verweigert."
+
+#: lib/Forms/Entry.php:166
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: lib/Forms/Entry.php:37
+msgid "Add Entry"
+msgstr "Eintrag hinzufügen"
+
+#: lib/Forms/CreateClass.php:97 lib/Forms/CreateClass.php:100
+#: lib/Forms/EditClass.php:85 lib/Forms/EditClass.php:88
+msgid "Address Book"
+msgstr "Adressbuch"
+
+#: search.php:101
+msgid "All Types"
+msgstr "Alle Typen"
+
+#: search.php:65
+msgid "All classes"
+msgstr "Alle Klassen"
+
+#: search.php:74
+msgid "All students"
+msgstr "Alle Studenten"
+
+#: config/schools.php.dist:90
+msgid "Appliance"
+msgstr "Anwenden"
+
+#: config/prefs.php.dist:81 config/prefs.php.dist:117
+msgid "Ascending"
+msgstr "Aufsteigend"
+
+#: config/prefs.php.dist:137
+msgid "Ask every time"
+msgstr "Jedes Mal nachfragen"
+
+#: config/prefs.php.dist:139
+msgid "Automatically create a new contact list"
+msgstr "Erstelle automatisch eine neue Kontaktliste."
+
+#: lib/Forms/DeleteClass.php:45 lib/Forms/DeleteClass.php:51
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: lib/Forms/CreateClass.php:65 lib/Forms/EditClass.php:67
+#: config/prefs.php.dist:55 config/prefs.php.dist:71
+#: templates/list/headers.inc:84
+msgid "Category"
+msgstr "Kategorie"
+
+#: classes/index.php:32 templates/classes/list.php:28
+msgid "Change Permissions"
+msgstr "Rechte Ändern"
+
+#: config/prefs.php.dist:18
+msgid "Change your settings for automatically create contact lists."
+msgstr ""
+"Ändern Sie die Einstellungen zum automatischen Erstellen einer Kontaktliste."
+
+#: config/prefs.php.dist:11
+msgid "Change your sorting and display options."
+msgstr "Ändern Sie die Sortierreihenfolge und andere Anzeigeeinstellungen."
+
+#: lib/School.php:94 lib/School.php:101 lib/School.php:130 lib/School.php:144
+#: lib/School.php:150 lib/School.php:166 lib/School.php:169
+#: lib/Forms/CreateClass.php:79 lib/Forms/CreateClass.php:100
+#: lib/Forms/Entry.php:62 lib/Forms/Entry.php:128 lib/Forms/EditClass.php:76
+#: lib/Forms/EditClass.php:88
+msgid "Choose:"
+msgstr "Auswählen:"
+
+#: data.php:66 lib/Forms/Entry.php:62 templates/classes/list.php:16
+#: templates/search/headers.inc:34
+msgid "Class"
+msgstr "Klasse"
+
+#: templates/classes/list.php:13
+msgid "Class List"
+msgstr "Klassenliste"
+
+#: templates/panel.inc:33 templates/panel.inc:34
+msgid "Classes"
+msgstr "Klassen"
+
+#: templates/list/header.inc:7 templates/search/header.inc:8
+msgid "Close Search"
+msgstr "Suche beenden"
+
+#: templates/data/export.inc:11
+msgid "Comma separated values (CSV)"
+msgstr "Kommagetrennte Werte (CSV)"
+
+#: lib/Forms/Entry.php:152 lib/Forms/Entry.php:161
+msgid "Comment"
+msgstr "Kommentar"
+
+#: data.php:129 search.php:159
+msgid "Completed"
+msgstr "Erledigt"
+
+#: data.php:82
+msgid "Completed outcomes"
+msgstr "Erledigte Lernziele"
+
+#: lib/Forms/Entry.php:151
+msgid "Completed?"
+msgstr "Erledigt?"
+
+#: config/schools.php.dist:112
+msgid "Concentration, attention, perseverance"
+msgstr "Konzentration, Aufmerksamkeit, Ausdauer"
+
+#: config/schools.php.dist:96
+msgid "Construct"
+msgstr "Gestalten"
+
+#: lib/Forms/CreateClass.php:144
+msgid "Contact List"
+msgstr "Kontaktliste"
+
+#: config/prefs.php.dist:17
+msgid "Contact Lists"
+msgstr "Kontaktlisten"
+
+#: lib/Forms/Entry.php:179
+msgid "Couldn't add the new entry."
+msgstr "Ein neuer Eintrag konnte nicht erstellt werden."
+
+#: lib/Forms/CreateClass.php:204 lib/Forms/EditClass.php:166
+msgid "Couldn't add the selected students to the class."
+msgstr ""
+"Die ausgewählten Studenten konnten nicht zur Klasse hinzugefügt werden."
+
+#: lib/Skoli.php:275 lib/Forms/CreateClass.php:228
+msgid "Couldn't create the contact list \"%s\"."
+msgstr "Die Kontaktliste \"%s\" konnte nicht erstellt werden."
+
+#: entry.php:64
+msgid "Couldn't update this entry: %s"
+msgstr "Der Eintrag konnte nicht aktualisiert werden. %s"
+
+#: lib/Forms/CreateClass.php:161
+msgid "Create"
+msgstr "Erstellen"
+
+#: lib/Forms/CreateClass.php:56
+msgid "Create Class"
+msgstr "Klasse erstellen"
+
+#: lib/Forms/CreateClass.php:146
+msgid "Create Contact List?"
+msgstr "Kontaktliste erstellen?"
+
+#: templates/classes/list.php:8
+msgid "Create a new Class"
+msgstr "Neue Klasse erstellen"
+
+#: lib/School.php:85
+msgid "Custom format:"
+msgstr "Eigenes Format:"
+
+#: config/schools.php.dist:48
+msgid "Custom school"
+msgstr "Eigene Schule"
+
+#: lib/Forms/Entry.php:86 templates/search/headers.inc:40
+msgid "Date"
+msgstr "Datum"
+
+#: config/prefs.php.dist:25
+msgid "Define a format for marks"
+msgstr "Formatdefinition für Noten"
+
+#: entry.php:94 classes/index.php:33 lib/Forms/DeleteClass.php:45
+#: templates/classes/list.php:30 templates/entry/delete.inc:8
+msgid "Delete"
+msgstr "Löschen"
+
+#: lib/Forms/DeleteClass.php:40
+msgid "Delete %s"
+msgstr "%s löschen"
+
+#: config/prefs.php.dist:82 config/prefs.php.dist:118
+msgid "Descending"
+msgstr "Absteigend"
+
+#: lib/Forms/CreateClass.php:63 lib/Forms/EditClass.php:65
+msgid "Description"
+msgstr "Beschreibung"
+
+#: config/prefs.php.dist:10
+msgid "Display Options"
+msgstr "Anzeige-Einstellungen"
+
+#: config/prefs.php.dist:138
+msgid "Don't create contact lists"
+msgstr "Kein Erstellen von Kontaktlisten"
+
+#: entry.php:91 classes/index.php:31 templates/classes/list.php:26
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: templates/list/classes.inc:13
+msgid "Edit \"%s\""
+msgstr "\"%s\" bearbeiten"
+
+#: lib/Forms/EditClass.php:56
+msgid "Edit %s"
+msgstr "%s bearbeiten"
+
+#: templates/list/headers.inc:44 templates/search/headers.inc:31
+msgid "Edit Class"
+msgstr "Klasse bearbeiten"
+
+#: entry.php:97 templates/search/entries.inc:5
+msgid "Edit Entry"
+msgstr "Eintrag bearbeiten"
+
+#: templates/list/headers.inc:89
+msgid "Edit categories and colors"
+msgstr "Kategorien und Farben bearbeiten"
+
+#: config/schools.php.dist:103
+msgid "English"
+msgstr "Englisch"
+
+#: config/prefs.php.dist:149
+msgid ""
+"Enter a default name for new contact lists.<br />NOTE: You can use %c, %g or "
+"%s as substitution for the class, grade respectively semester name."
+msgstr ""
+"Eingabe eines Standardnamens für neue Kontaktlisten.<br />HINWEIS: Sie "
+"können %c, %g oder %s als Ersatz für die Klasse (class), Stufe (grade) und "
+"Semester (semester) verwenden."
+
+#: config/prefs.php.dist:167
+msgid ""
+"Enter some custom marks and separate them by comma (best mark first).<br /"
+">NOTE: You also need to choose \"Custom settings\" above."
+msgstr ""
+"Eingabe eines eigenen Notenformats. Jede Note wird mit einem Komma getrennt "
+"(beste Note zuerst).<br />HINWEIS: Sie müssen auch den Punkt \"Eigene "
+"Einstellungen\" weiter oben auswählen."
+
+#: templates/search/headers.inc:46
+msgid "Entry"
+msgstr "Eintrag"
+
+#: entry.php:103
+msgid "Entry for \"%s\""
+msgstr "Eintrag für \"%s\""
+
+#: entry.php:28
+msgid "Entry not found."
+msgstr "Eintrag nicht gefunden."
+
+#: lib/School.php:39
+msgid "Error loading the school \"%s\" from template."
+msgstr "Fehler beim Laden der Schule \"%s\" aus der Vorlage."
+
+#: data.php:137 search.php:164
+msgid "Excused"
+msgstr "Entschuldigt"
+
+#: data.php:72
+msgid "Excused absences"
+msgstr "Entschuldigte Abwesenheiten"
+
+#: lib/Forms/Entry.php:157
+msgid "Excused?"
+msgstr "Entschuldigt?"
+
+#: config/schools.php.dist:113
+msgid "Exercise processing"
+msgstr "Aufgabenbearbeitung"
+
+#: templates/data/export.inc:32
+msgid "Export"
+msgstr "Exportieren"
+
+#: data.php:177 templates/data/export.inc:5
+msgid "Export Classes"
+msgstr "Klassen exportieren"
+
+#: data.php:67
+msgid "Firstname"
+msgstr "Vorname"
+
+#: lib/School.php:83
+msgid "Format in numbers"
+msgstr "Format in Zahlen"
+
+#: lib/School.php:84
+msgid "Format in percent"
+msgstr "Format in Prozent"
+
+#: config/schools.php.dist:97
+msgid "French"
+msgstr "Französisch"
+
+#: config/prefs.php.dist:9 config/prefs.php.dist:16 config/prefs.php.dist:23
+msgid "General Options"
+msgstr "Allgemeine Einstellungen"
+
+#: lib/Forms/CreateClass.php:60 lib/Forms/EditClass.php:62
+msgid "General Settings"
+msgstr "Allgemeine Angaben"
+
+#: config/schools.php.dist:82
+msgid "German"
+msgstr "Deutsch"
+
+#: templates/list/headers.inc:72 config/prefs.php.dist:52
+#: config/prefs.php.dist:68
+msgid "Grade"
+msgstr "Stufe"
+
+#: config/schools.php.dist:98 config/schools.php.dist:104
+msgid "Hearing"
+msgstr "Hörverstehen"
+
+#: config/schools.php.dist:83
+msgid "Hearing and Talking"
+msgstr "Hören und Sprechen"
+
+#: config/prefs.php.dist:128
+msgid ""
+"How many characters of the entry details in search view should we allow to "
+"see?"
+msgstr ""
+"Wieviele Zeichen sollen bei den Eintragdetails in der Suchansicht gezeigt "
+"werden?"
+
+#: config/prefs.php.dist:158
+msgid "How many decimal digits should we round marks to?"
+msgstr "Auf wieviele Stellen sollen die Noten gerundet werden?"
+
+#: config/schools.php.dist:88
+msgid "Imagination"
+msgstr "Vorstellungsvermögen"
+
+#: lib/School.php:123 lib/School.php:127 lib/School.php:138 lib/School.php:139
+#: lib/School.php:145
+msgid "Interdisciplinary"
+msgstr "Fächerübergreifend"
+
+#: templates/list/headers.inc:60 config/prefs.php.dist:92
+#: config/prefs.php.dist:105
+msgid "Last Entry"
+msgstr "Letzter Eintrag"
+
+#: data.php:68
+msgid "Lastname"
+msgstr "Nachname"
+
+#: lib/Skoli.php:497 config/prefs.php.dist:35
+msgid "List Classes"
+msgstr "Klassen anzeigen"
+
+#: lib/School.php:104
+msgid "List with custom marks separated by comma (best mark first)"
+msgstr "Kommagetrennte Liste mit eigenen Noten (beste Note zuerst)"
+
+#: templates/list/headers.inc:80 config/prefs.php.dist:54
+#: config/prefs.php.dist:70
+msgid "Location"
+msgstr "Ort"
+
+#: classes/index.php:37 templates/classes/list.php:2
+msgid "Manage Classes"
+msgstr "Klassen-Verwaltung"
+
+#: lib/Skoli.php:312 lib/Forms/Entry.php:97 lib/Forms/Entry.php:119
+#: lib/Forms/Entry.php:123 lib/Forms/Entry.php:128
+msgid "Mark"
+msgstr "Note"
+
+#: templates/list/headers.inc:64 config/prefs.php.dist:93
+#: config/prefs.php.dist:106
+msgid "Mark average"
+msgstr "Notendurchschnitt"
+
+#: lib/Forms/Entry.php:119
+msgid "Mark in numbers"
+msgstr "Note in Zahlen"
+
+#: lib/Forms/Entry.php:123
+msgid "Mark in percent"
+msgstr "Note in Prozent"
+
+#: data.php:106 search.php:104 config/prefs.php.dist:24
+msgid "Marks"
+msgstr "Noten"
+
+#: config/schools.php.dist:87
+msgid "Mathematics"
+msgstr "Mathematik"
+
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr "Menüliste"
+
+#: config/schools.php.dist:111
+msgid "Motivation to learn and dedication"
+msgstr "Lernmotivation und Einsatz"
+
+#: config/schools.php.dist:94
+msgid "Music"
+msgstr "Musik"
+
+#: list.php:16
+msgid "My Classes"
+msgstr "Meine Klassen"
+
+#: templates/panel.inc:54
+msgid "My Classes:"
+msgstr "Meine Klassen:"
+
+#: lib/Forms/CreateClass.php:62 lib/Forms/CreateClass.php:151
+#: lib/Forms/EditClass.php:64 templates/list/headers.inc:56
+#: config/prefs.php.dist:67 config/prefs.php.dist:104
+msgid "Name"
+msgstr "Name"
+
+#: config/schools.php.dist:93
+msgid "Nature-Human-Environment"
+msgstr "Natur-Mensch-Mitwelt"
+
+#: lib/Block/tree_menu.php:29 templates/list/students.inc:5
+#: templates/list/classes.inc:5 templates/list/headers.inc:41
+#: config/prefs.php.dist:36
+msgid "New Entry"
+msgstr "Neuer Eintrag"
+
+#: data.php:25
+msgid "No classes are currently available. Export is disabled."
+msgstr ""
+"Zur Zeit sind keine Klassen verfügbar. Export steht nicht zur Verfügung."
+
+#: search.php:19
+msgid "No classes are currently available. Searching is disabled."
+msgstr ""
+"Zur Zeit sind keine Klassen verfügbar. Die Suche steht nicht zur Verfügung."
+
+#: templates/list/footers.inc:4
+msgid "No classes match"
+msgstr "Keine Treffer"
+
+#: templates/search/footers.inc:4
+msgid "No entries match"
+msgstr "Keine Treffer"
+
+#: data.php:137 search.php:164
+msgid "Not excused"
+msgstr "Unentschuldigt"
+
+#: lib/Skoli.php:313 lib/Forms/Entry.php:100 lib/Forms/Entry.php:146
+msgid "Objective"
+msgstr "Beobachtung"
+
+#: data.php:116 search.php:108
+msgid "Objectives"
+msgstr "Beobachtungen"
+
+#: data.php:129 search.php:159
+msgid "Open"
+msgstr "Offen"
+
+#: data.php:83
+msgid "Open outcomes"
+msgstr "Offene Lernziele"
+
+#: lib/Skoli.php:314 lib/Forms/Entry.php:103 lib/Forms/Entry.php:150
+msgid "Outcome"
+msgstr "Lernziel"
+
+#: data.php:126 search.php:112
+msgid "Outcomes"
+msgstr "Lernziele"
+
+#: templates/entry/delete.inc:7
+msgid "Permanently delete this entry?"
+msgstr "Diesen Eintrag unwiederbringlich löschen?"
+
+#: lib/Forms/DeleteClass.php:56
+msgid "Permission denied"
+msgstr "Zugriff verweigert"
+
+#: list.php:59 add.php:19
+msgid "Please create a new Class first."
+msgstr "Bitte erstellen Sie zuerst eine neue Klasse."
+
+#: config/schools.php.dist:91
+msgid "Problem solving behavior"
+msgstr "Problemlöseverhalten"
+
+#: lib/Forms/CreateClass.php:58 lib/Forms/EditClass.php:60
+msgid "Properties"
+msgstr "Eigenschaften"
+
+#: config/schools.php.dist:84 config/schools.php.dist:100
+#: config/schools.php.dist:106
+msgid "Reading"
+msgstr "Lesen"
+
+#: lib/Forms/DeleteClass.php:43
+msgid ""
+"Really delete the class \"%s\"? This cannot be undone and all data on this "
+"class will be permanently removed."
+msgstr ""
+"Die Klasse \"%s\" wirklich löschen? Dieser Vorgang kann nicht rückgängig "
+"gemacht werden, und alle Daten in dieser Klasse werden endgültig gelöscht."
+
+#: templates/search/criteria.inc:39
+msgid "Reset to Defaults"
+msgstr "Zurücksetzen"
+
+#: config/schools.php.dist:55
+msgid "Sample school"
+msgstr "Beispiel Schule"
+
+#: lib/Forms/Entry.php:166 lib/Forms/EditClass.php:131 templates/panel.inc:72
+msgid "Save"
+msgstr "Speichern"
+
+#: lib/Forms/EditClass.php:76
+msgid "School"
+msgstr "Schule"
+
+#: lib/Forms/CreateClass.php:72 lib/Forms/EditClass.php:69
+msgid "School Specific Settings"
+msgstr "Schulabhängige Einstellungen"
+
+#: config/schools.php.dist:77
+msgid "Schoolhouse 1"
+msgstr "Schulhaus 1"
+
+#: config/schools.php.dist:78
+msgid "Schoolhouse 2"
+msgstr "Schulhaus 2"
+
+#: lib/Forms/CreateClass.php:79
+msgid "Schools"
+msgstr "Schulen"
+
+#: search.php:119 lib/Block/tree_menu.php:48 templates/list/header.inc:3
+#: templates/search/header.inc:4 templates/search/criteria.inc:38
+#: config/prefs.php.dist:37
+msgid "Search"
+msgstr "Suche"
+
+#: templates/search/criteria.inc:9
+msgid "Search Criterias"
+msgstr "Suchkriterien"
+
+#: templates/search/header.inc:3
+msgid "Search Result"
+msgstr "Suchergebnisse"
+
+#: templates/panel.inc:39
+msgid "Search for Classes:"
+msgstr "Nach Klassen suchen:"
+
+#: templates/search/criteria.inc:13
+msgid "Search in"
+msgstr "Suchen in"
+
+#: list.php:90
+msgid "Search: Results for \"%s\""
+msgstr "Suche: Ergebnisse für \"%s\""
+
+#: templates/data/export.inc:26
+msgid "Select a student or the whole class to export:"
+msgstr "Wählen Sie einen Studenten oder die ganze Klasse zum Exportieren:"
+
+#: templates/data/export.inc:17
+msgid "Select the class to export from:"
+msgstr "Wählen Sie die Klasse die exportiert werden soll"
+
+#: config/prefs.php.dist:56
+msgid "Select the columns that should be shown in the class list view:"
+msgstr ""
+"Wählen Sie die Spalten, die in der Listenansicht der Klassen angezeigt "
+"werden sollen:"
+
+#: config/prefs.php.dist:95
+msgid "Select the columns that should be shown in the student list view:"
+msgstr ""
+"Wählen Sie die Spalten, die in der Listenansicht der Studierenden angezeigt "
+"werden sollen:"
+
+#: templates/data/export.inc:9
+msgid "Select the export format:"
+msgstr "Wählen Sie das Exportformat:"
+
+#: config/prefs.php.dist:38
+msgid "Select the view to display after login:"
+msgstr "Wählen Sie die Ansicht aus, die beim Start angezeigt werden soll:"
+
+#: templates/list/headers.inc:76 config/prefs.php.dist:53
+#: config/prefs.php.dist:69
+msgid "Semester"
+msgstr "Semester"
+
+#: templates/list/headers.inc:52 config/prefs.php.dist:51
+#: config/prefs.php.dist:66
+msgid "Semester End"
+msgstr "Semesterende"
+
+#: templates/list/headers.inc:48 config/prefs.php.dist:50
+#: config/prefs.php.dist:65
+msgid "Semester Start"
+msgstr "Semesterstart"
+
+#: templates/panel.inc:63
+msgid "Shared Classes:"
+msgstr "Gemeinsame Klassen:"
+
+#: templates/list/students.inc:13
+msgid "Show \"%s\""
+msgstr "\"%s\" anzeigen"
+
+#: config/prefs.php.dist:181
+msgid "Show class list options panel?"
+msgstr "Kasten mit Klassenlisteninstellungen anzeigen?"
+
+#: config/prefs.php.dist:190
+msgid "Show students in the class list?"
+msgstr "Studierende in der Klassenliste anzeigen?"
+
+#: templates/panel.inc:44
+msgid "Show students?"
+msgstr "Studierende anzeigen?"
+
+#: config/schools.php.dist:89
+msgid "Skills"
+msgstr "Kenntnisse, Fertigkeiten"
+
+#: list.php:54
+msgid "Skoli needs an applications who provides contacts (e.g. turba)."
+msgstr "Skoli benötigt eine Applikation mit Kontakten (z.B. turba)."
+
+#: templates/list/headers.inc:68
+msgid "Sort by Absences"
+msgstr "Sortieren nach Abwesenheiten"
+
+#: templates/list/headers.inc:84
+msgid "Sort by Category"
+msgstr "Sortieren nach Kategorie"
+
+#: templates/search/headers.inc:34
+msgid "Sort by Class"
+msgstr "Sortieren nach Klasse"
+
+#: templates/search/headers.inc:40
+msgid "Sort by Date"
+msgstr "Sortieren nach Datum"
+
+#: templates/list/headers.inc:72
+msgid "Sort by Grade"
+msgstr "Sortieren nach Stufe"
+
+#: templates/list/headers.inc:60
+msgid "Sort by Last Entry"
+msgstr "Sortieren nach letztem Eintrag"
+
+#: templates/list/headers.inc:80
+msgid "Sort by Location"
+msgstr "Sortieren nach Ort"
+
+#: templates/list/headers.inc:64
+msgid "Sort by Mark"
+msgstr "Sortieren nach Noten"
+
+#: templates/list/headers.inc:56
+msgid "Sort by Name"
+msgstr "Sortieren nach Name"
+
+#: templates/list/headers.inc:76
+msgid "Sort by Semester"
+msgstr "Sortieren nach Semester"
+
+#: templates/list/headers.inc:52
+msgid "Sort by Semester End Date"
+msgstr "Sortieren nach Semesterenddatum"
+
+#: templates/list/headers.inc:48
+msgid "Sort by Semester Start Date"
+msgstr "Sortieren nach Semesterstartdatum"
+
+#: templates/search/headers.inc:37
+msgid "Sort by Student Name"
+msgstr "Sortieren nach Studierenden"
+
+#: templates/search/headers.inc:43
+msgid "Sort by Type"
+msgstr "Sortieren nach Typ"
+
+#: config/prefs.php.dist:72
+msgid "Sort classes by:"
+msgstr "Klassen sortieren nach:"
+
+#: config/prefs.php.dist:83
+msgid "Sort direction for classes:"
+msgstr "Sortierrichtung für Klassen:"
+
+#: config/prefs.php.dist:119
+msgid "Sort direction for students:"
+msgstr "Sortierrichtung für Studierende:"
+
+#: config/prefs.php.dist:108
+msgid "Sort students by:"
+msgstr "Studierende sortieren nach:"
+
+#: config/schools.php.dist:95
+msgid "Sport"
+msgstr "Sport"
+
+#: lib/Forms/Entry.php:78 lib/Forms/Entry.php:80
+#: templates/search/headers.inc:37
+msgid "Student"
+msgstr "Studierender"
+
+#: lib/Forms/CreateClass.php:95 lib/Forms/CreateClass.php:108
+#: lib/Forms/CreateClass.php:141 lib/Forms/EditClass.php:83
+#: lib/Forms/EditClass.php:96 lib/Forms/EditClass.php:129
+msgid "Students"
+msgstr "Studierende"
+
+#: templates/data/export.inc:12
+msgid "Tab separated values (TSV)"
+msgstr "Tabgetrennte Werte (TSV)"
+
+#: config/schools.php.dist:99 config/schools.php.dist:105
+msgid "Talking"
+msgstr "Sprechen"
+
+#: config/schools.php.dist:114
+msgid "Teamwork and autonomy"
+msgstr "Zusammenarbeit und Selbstständigkeit"
+
+#: lib/Driver.php:38
+msgid "The School backend is not currently available."
+msgstr "Der Schuleserver ist zur Zeit nicht verfügbar."
+
+#: lib/Driver.php:87
+msgid "The School backend is not currently available: %s"
+msgstr "Der Schuleserver ist zur Zeit nicht verfügbar: %s"
+
+#: classes/create.php:38
+msgid "The class \"%s\" has been created."
+msgstr "Die Klasse \"%s\" wurde erstellt."
+
+#: classes/delete.php:43
+msgid "The class \"%s\" has been deleted."
+msgstr "Die Klasse \"%s\" wurde gelöscht."
+
+#: classes/edit.php:50
+msgid "The class \"%s\" has been renamed to \"%s\"."
+msgstr "Die Klasse \"%s\" wurde nach \"%s\" umbenannt."
+
+#: classes/edit.php:52
+msgid "The class \"%s\" has been saved."
+msgstr "Die Klasse \"%s\" wurde gespeichert."
+
+#: entry.php:80
+msgid "The entry for \"%s\" has been deleted."
+msgstr "Der Eintrag \"%s\" wurde gelöscht."
+
+#: entry.php:66
+msgid "The entry for \"%s\" has been saved."
+msgstr "Der Eintrag für \"%s\" wurde gespeichert."
+
+#: add.php:33
+msgid "The new entry for \"%s\" has been added."
+msgstr "Der neue Eintrag für \"%s\" wurde hinzugefügt."
+
+#: lib/Forms/CreateClass.php:152
+msgid ""
+"The substitutions %c, %g or %s will be replaced automatically by the class, "
+"grade respectively semester name."
+msgstr ""
+"Die Substitutionen %c, %g oder %s werden automatisch mit der Klasse (class), "
+"Stufe (grade) bzw. dem Semester (semester) ersetzt."
+
+#: templates/list/empty.inc:2
+msgid "There are no classes matching the current criteria."
+msgstr "Es gibt keine Klassen, die den Suchkriterien entsprechen."
+
+#: templates/search/empty.inc:2
+msgid "There are no entries matching the current criteria."
+msgstr "Es wurden keine passenden Einträge gefunden."
+
+#: entry.php:78
+msgid "There was an error deleting this entry: %s"
+msgstr "Beim Löschen des Eintrags ist ein Fehler aufgetreten: %s"
+
+#: data.php:158
+msgid "There were no entries to export."
+msgstr "Es konnten keine Einträge zum Exportieren gefunden werden."
+
+#: index.php:20
+msgid "This file defines templates for new classes."
+msgstr ""
+
+#: lib/Forms/Entry.php:116
+msgid "Title"
+msgstr "Titel"
+
+#: lib/Forms/Entry.php:108 templates/search/headers.inc:43
+msgid "Type"
+msgstr "Typ"
+
+#: lib/Forms/DeleteClass.php:63
+msgid "Unable to delete \"%s\": %s"
+msgstr "\"%s\" kann nicht gelöscht werden: %s"
+
+#: lib/Driver.php:90
+msgid "Unable to load the definition of %s."
+msgstr "Der %s-Treiber konnte nicht geladen werden."
+
+#: lib/Forms/EditClass.php:171
+msgid "Unable to save class \"%s\": %s"
+msgstr "Die Klasse \"%s\" kann nicht gespeichert werden: %s"
+
+#: lib/Skoli.php:753 lib/Skoli.php:754 lib/Skoli.php:768 lib/Skoli.php:769
+#: templates/list/classes.inc:46
+msgid "Unfiled"
+msgstr "Nicht zugeordnet"
+
+#: lib/Forms/Entry.php:37
+msgid "Update Entry"
+msgstr "Eintrag aktualisieren"
+
+#: entry.php:89
+msgid "View"
+msgstr "Anzeigen"
+
+#: templates/list/students.inc:27
+msgid "View Entries for \"%s\""
+msgstr "Einträge anzeigen für \"%s\""
+
+#: templates/list/classes.inc:25
+msgid "View Entries in \"%s\""
+msgstr "Einträge anzeigen in \"%s\""
+
+#: templates/search/entries.inc:21
+msgid "View Entry"
+msgstr "Eintrag anzeigen"
+
+#: lib/Forms/Entry.php:134
+msgid "Weight"
+msgstr "Gewichtung"
+
+#: config/prefs.php.dist:140
+msgid "When a new class is created should we also create a new contact list?"
+msgstr ""
+"Soll beim Erstellen einer neuen Klasse auch eine neue Kontaktliste angelegt "
+"werden?"
+
+#: data.php:39
+msgid "Whole class"
+msgstr "Ganze Klasse"
+
+#: config/schools.php.dist:85 config/schools.php.dist:101
+#: config/schools.php.dist:107
+msgid "Writing"
+msgstr "Schreiben"
+
+#: classes/edit.php:28
+msgid "You are not allowed to change this class."
+msgstr "Sie dürfen diese Klasse nicht ändern."
+
+#: classes/delete.php:30
+msgid "You are not allowed to delete this class."
+msgstr "Sie dürfen diese Klasse nicht löschen."
+
+#: entry.php:36
+msgid "You are not allowed to view this entry."
+msgstr "Sie dürfen diesen Eintrag nicht ansehen."
+
+#: classes/create.php:24
+msgid "You don't have access to any valid addressbook."
+msgstr "Sie haben keinen Zugriff auf ein gültiges Adressbuch."
+
+#: templates/panel.inc:49
+msgid "[Manage Classes]"
+msgstr "[Klassen verwalten]"
+
+#: lib/Skoli.php:507
+msgid "_Export"
+msgstr "_Exportieren"
+
+#: lib/Skoli.php:499
+msgid "_New Entry"
+msgstr "_Neuer Eintrag"
+
+#: lib/Skoli.php:503
+msgid "_Search"
+msgstr "_Suche"
+
+#: templates/search/criteria.inc:21
+msgid "and"
+msgstr "und"
+
+#: templates/search/criteria.inc:35
+msgid "and for Entries with:"
+msgstr "und für Einträge mit:"
+
+#: data.php:165 templates/data/export.inc:1
+msgid "class.csv"
+msgstr "klasse.csv"
+
+#: data.php:170
+msgid "class.tsv"
+msgstr "klasse.tsv"
+
+#: templates/search/criteria.inc:25
+msgid "for"
+msgstr "für"
+
+#: lib/Block/tree_menu.php:39
+msgid "in %s"
+msgstr "in %s"
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Horde Project
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+"POT-Creation-Date: 2009-04-16 11:04+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: config/schools.php.dist:57
+msgid "1. class"
+msgstr ""
+
+#: config/schools.php.dist:66
+msgid "1. term"
+msgstr ""
+
+#: config/schools.php.dist:58
+msgid "2. class"
+msgstr ""
+
+#: config/schools.php.dist:71
+msgid "2. term"
+msgstr ""
+
+#: config/schools.php.dist:59
+msgid "3. class"
+msgstr ""
+
+#: config/schools.php.dist:60
+msgid "4. class"
+msgstr ""
+
+#: config/schools.php.dist:61
+msgid "5. class"
+msgstr ""
+
+#: config/schools.php.dist:62
+msgid "6. class"
+msgstr ""
+
+#: lib/Skoli.php:315 lib/Forms/Entry.php:106 lib/Forms/Entry.php:156
+msgid "Absence"
+msgstr ""
+
+#: lib/Forms/Entry.php:156
+msgid "Absence in number of lessons"
+msgstr ""
+
+#: data.php:134 search.php:116 templates/list/headers.inc:68
+#: config/prefs.php.dist:94 config/prefs.php.dist:107
+msgid "Absences"
+msgstr ""
+
+#: data.php:73
+msgid "Absences without valid excuse"
+msgstr ""
+
+#: lib/Driver.php:68
+#, php-format
+msgid "Access for class \"%s.\" is denied"
+msgstr ""
+
+#: lib/Forms/Entry.php:166
+msgid "Add"
+msgstr ""
+
+#: lib/Forms/Entry.php:37
+msgid "Add Entry"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:97 lib/Forms/CreateClass.php:100
+#: lib/Forms/EditClass.php:85 lib/Forms/EditClass.php:88
+msgid "Address Book"
+msgstr ""
+
+#: search.php:101
+msgid "All Types"
+msgstr ""
+
+#: search.php:65
+msgid "All classes"
+msgstr ""
+
+#: search.php:74
+msgid "All students"
+msgstr ""
+
+#: config/schools.php.dist:90
+msgid "Appliance"
+msgstr ""
+
+#: config/prefs.php.dist:81 config/prefs.php.dist:117
+msgid "Ascending"
+msgstr ""
+
+#: config/prefs.php.dist:137
+msgid "Ask every time"
+msgstr ""
+
+#: config/prefs.php.dist:139
+msgid "Automatically create a new contact list"
+msgstr ""
+
+#: lib/Forms/DeleteClass.php:45 lib/Forms/DeleteClass.php:51
+msgid "Cancel"
+msgstr ""
+
+#: templates/list/headers.inc:84
+msgid "Cat_egory"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:65 lib/Forms/EditClass.php:67
+#: config/prefs.php.dist:55 config/prefs.php.dist:71
+msgid "Category"
+msgstr ""
+
+#: classes/index.php:32 templates/classes/list.php:28
+msgid "Change Permissions"
+msgstr ""
+
+#: config/prefs.php.dist:18
+msgid "Change your settings for automatically create contact lists."
+msgstr ""
+
+#: config/prefs.php.dist:11
+msgid "Change your sorting and display options."
+msgstr ""
+
+#: lib/School.php:94 lib/School.php:101 lib/School.php:130 lib/School.php:144
+#: lib/School.php:150 lib/School.php:166 lib/School.php:169
+#: lib/Forms/CreateClass.php:79 lib/Forms/CreateClass.php:100
+#: lib/Forms/Entry.php:62 lib/Forms/Entry.php:128 lib/Forms/EditClass.php:76
+#: lib/Forms/EditClass.php:88
+msgid "Choose:"
+msgstr ""
+
+#: data.php:66 lib/Forms/Entry.php:62 templates/classes/list.php:16
+#: templates/search/headers.inc:34
+msgid "Class"
+msgstr ""
+
+#: templates/classes/list.php:13
+msgid "Class List"
+msgstr ""
+
+#: templates/panel.inc:33 templates/panel.inc:34
+msgid "Classes"
+msgstr ""
+
+#: templates/list/header.inc:7 templates/search/header.inc:8
+msgid "Close Search"
+msgstr ""
+
+#: templates/data/export.inc:11
+msgid "Comma separated values (CSV)"
+msgstr ""
+
+#: lib/Forms/Entry.php:152 lib/Forms/Entry.php:161
+msgid "Comment"
+msgstr ""
+
+#: data.php:129 search.php:159
+msgid "Completed"
+msgstr ""
+
+#: data.php:82
+msgid "Completed outcomes"
+msgstr ""
+
+#: lib/Forms/Entry.php:151
+msgid "Completed?"
+msgstr ""
+
+#: config/schools.php.dist:112
+msgid "Concentration, attention, perseverance"
+msgstr ""
+
+#: config/schools.php.dist:96
+msgid "Construct"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:144
+msgid "Contact List"
+msgstr ""
+
+#: config/prefs.php.dist:17
+msgid "Contact Lists"
+msgstr ""
+
+#: lib/Forms/Entry.php:179
+msgid "Couldn't add the new entry."
+msgstr ""
+
+#: lib/Forms/CreateClass.php:204 lib/Forms/EditClass.php:166
+msgid "Couldn't add the selected students to the class."
+msgstr ""
+
+#: lib/Skoli.php:275 lib/Forms/CreateClass.php:228
+#, php-format
+msgid "Couldn't create the contact list \"%s\"."
+msgstr ""
+
+#: entry.php:64
+#, php-format
+msgid "Couldn't update this entry: %s"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:161
+msgid "Create"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:56
+msgid "Create Class"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:146
+msgid "Create Contact List?"
+msgstr ""
+
+#: templates/classes/list.php:8
+msgid "Create a new Class"
+msgstr ""
+
+#: lib/School.php:85
+msgid "Custom format:"
+msgstr ""
+
+#: config/schools.php.dist:48
+msgid "Custom school"
+msgstr ""
+
+#: lib/Forms/Entry.php:86 templates/search/headers.inc:40
+msgid "Date"
+msgstr ""
+
+#: config/prefs.php.dist:25
+msgid "Define a format for marks"
+msgstr ""
+
+#: entry.php:94 classes/index.php:33 lib/Forms/DeleteClass.php:45
+#: templates/classes/list.php:30 templates/entry/delete.inc:8
+msgid "Delete"
+msgstr ""
+
+#: lib/Forms/DeleteClass.php:40
+#, php-format
+msgid "Delete %s"
+msgstr ""
+
+#: config/prefs.php.dist:82 config/prefs.php.dist:118
+msgid "Descending"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:63 lib/Forms/EditClass.php:65
+msgid "Description"
+msgstr ""
+
+#: config/prefs.php.dist:10
+msgid "Display Options"
+msgstr ""
+
+#: config/prefs.php.dist:138
+msgid "Don't create contact lists"
+msgstr ""
+
+#: entry.php:91 classes/index.php:31 templates/classes/list.php:26
+msgid "Edit"
+msgstr ""
+
+#: templates/list/classes.inc:13
+#, php-format
+msgid "Edit \"%s\""
+msgstr ""
+
+#: lib/Forms/EditClass.php:56
+#, php-format
+msgid "Edit %s"
+msgstr ""
+
+#: templates/list/headers.inc:44 templates/search/headers.inc:31
+msgid "Edit Class"
+msgstr ""
+
+#: entry.php:97 templates/search/entries.inc:5
+msgid "Edit Entry"
+msgstr ""
+
+#: templates/list/headers.inc:89
+msgid "Edit categories and colors"
+msgstr ""
+
+#: config/schools.php.dist:103
+msgid "English"
+msgstr ""
+
+#: config/prefs.php.dist:149
+msgid ""
+"Enter a default name for new contact lists.<br />NOTE: You can use %c, %g or "
+"%s as substitution for the class, grade respectively semester name."
+msgstr ""
+
+#: config/prefs.php.dist:167
+msgid ""
+"Enter some custom marks and separate them by comma (best mark first).<br /"
+">NOTE: You also need to choose \"Custom settings\" above."
+msgstr ""
+
+#: templates/search/headers.inc:46
+msgid "Entry"
+msgstr ""
+
+#: entry.php:103
+#, php-format
+msgid "Entry for \"%s\""
+msgstr ""
+
+#: entry.php:28
+msgid "Entry not found."
+msgstr ""
+
+#: lib/School.php:39
+#, php-format
+msgid "Error loading the school \"%s\" from template."
+msgstr ""
+
+#: data.php:137 search.php:164
+msgid "Excused"
+msgstr ""
+
+#: data.php:72
+msgid "Excused absences"
+msgstr ""
+
+#: lib/Forms/Entry.php:157
+msgid "Excused?"
+msgstr ""
+
+#: config/schools.php.dist:113
+msgid "Exercise processing"
+msgstr ""
+
+#: templates/data/export.inc:32
+msgid "Export"
+msgstr ""
+
+#: data.php:177 templates/data/export.inc:5
+msgid "Export Classes"
+msgstr ""
+
+#: data.php:67
+msgid "Firstname"
+msgstr ""
+
+#: lib/School.php:83
+msgid "Format in numbers"
+msgstr ""
+
+#: lib/School.php:84
+msgid "Format in percent"
+msgstr ""
+
+#: config/schools.php.dist:97
+msgid "French"
+msgstr ""
+
+#: config/prefs.php.dist:9 config/prefs.php.dist:16 config/prefs.php.dist:23
+msgid "General Options"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:60 lib/Forms/EditClass.php:62
+msgid "General Settings"
+msgstr ""
+
+#: config/schools.php.dist:82
+msgid "German"
+msgstr ""
+
+#: templates/list/headers.inc:72 config/prefs.php.dist:52
+#: config/prefs.php.dist:68
+msgid "Grade"
+msgstr ""
+
+#: config/schools.php.dist:98 config/schools.php.dist:104
+msgid "Hearing"
+msgstr ""
+
+#: config/schools.php.dist:83
+msgid "Hearing and Talking"
+msgstr ""
+
+#: config/prefs.php.dist:128
+msgid ""
+"How many characters of the entry details in search view should we allow to "
+"see?"
+msgstr ""
+
+#: config/prefs.php.dist:158
+msgid "How many decimal digits should we round marks to?"
+msgstr ""
+
+#: config/schools.php.dist:88
+msgid "Imagination"
+msgstr ""
+
+#: lib/School.php:123 lib/School.php:127 lib/School.php:138 lib/School.php:139
+#: lib/School.php:145
+msgid "Interdisciplinary"
+msgstr ""
+
+#: templates/list/headers.inc:60 config/prefs.php.dist:92
+#: config/prefs.php.dist:105
+msgid "Last Entry"
+msgstr ""
+
+#: data.php:68
+msgid "Lastname"
+msgstr ""
+
+#: lib/Skoli.php:497 config/prefs.php.dist:35
+msgid "List Classes"
+msgstr ""
+
+#: lib/School.php:104
+msgid "List with custom marks separated by comma (best mark first)"
+msgstr ""
+
+#: templates/list/headers.inc:80 config/prefs.php.dist:54
+#: config/prefs.php.dist:70
+msgid "Location"
+msgstr ""
+
+#: classes/index.php:37 templates/classes/list.php:2
+msgid "Manage Classes"
+msgstr ""
+
+#: lib/Skoli.php:312 lib/Forms/Entry.php:97 lib/Forms/Entry.php:119
+#: lib/Forms/Entry.php:123 lib/Forms/Entry.php:128
+msgid "Mark"
+msgstr ""
+
+#: templates/list/headers.inc:64
+msgid "Mark Average"
+msgstr ""
+
+#: config/prefs.php.dist:93 config/prefs.php.dist:106
+msgid "Mark average"
+msgstr ""
+
+#: lib/Forms/Entry.php:119
+msgid "Mark in numbers"
+msgstr ""
+
+#: lib/Forms/Entry.php:123
+msgid "Mark in percent"
+msgstr ""
+
+#: data.php:106 search.php:104 config/prefs.php.dist:24
+msgid "Marks"
+msgstr ""
+
+#: config/schools.php.dist:87
+msgid "Mathematics"
+msgstr ""
+
+#: lib/Block/tree_menu.php:3
+msgid "Menu List"
+msgstr ""
+
+#: config/schools.php.dist:111
+msgid "Motivation to learn and dedication"
+msgstr ""
+
+#: config/schools.php.dist:94
+msgid "Music"
+msgstr ""
+
+#: list.php:16
+msgid "My Classes"
+msgstr ""
+
+#: templates/panel.inc:54
+msgid "My Classes:"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:62 lib/Forms/CreateClass.php:151
+#: lib/Forms/EditClass.php:64 templates/list/headers.inc:56
+#: config/prefs.php.dist:67 config/prefs.php.dist:104
+msgid "Name"
+msgstr ""
+
+#: config/schools.php.dist:93
+msgid "Nature-Human-Environment"
+msgstr ""
+
+#: lib/Block/tree_menu.php:29 templates/list/students.inc:5
+#: templates/list/classes.inc:5 templates/list/headers.inc:41
+#: config/prefs.php.dist:36
+msgid "New Entry"
+msgstr ""
+
+#: data.php:25
+msgid "No classes are currently available. Export is disabled."
+msgstr ""
+
+#: search.php:19
+msgid "No classes are currently available. Searching is disabled."
+msgstr ""
+
+#: templates/list/footers.inc:4
+msgid "No classes match"
+msgstr ""
+
+#: templates/search/footers.inc:4
+msgid "No entries match"
+msgstr ""
+
+#: data.php:137 search.php:164
+msgid "Not excused"
+msgstr ""
+
+#: lib/Skoli.php:313 lib/Forms/Entry.php:100 lib/Forms/Entry.php:146
+msgid "Objective"
+msgstr ""
+
+#: data.php:116 search.php:108
+msgid "Objectives"
+msgstr ""
+
+#: data.php:129 search.php:159
+msgid "Open"
+msgstr ""
+
+#: data.php:83
+msgid "Open outcomes"
+msgstr ""
+
+#: lib/Skoli.php:314 lib/Forms/Entry.php:103 lib/Forms/Entry.php:150
+msgid "Outcome"
+msgstr ""
+
+#: data.php:126 search.php:112
+msgid "Outcomes"
+msgstr ""
+
+#: templates/entry/delete.inc:7
+msgid "Permanently delete this entry?"
+msgstr ""
+
+#: lib/Forms/DeleteClass.php:56
+msgid "Permission denied"
+msgstr ""
+
+#: list.php:59 add.php:19
+msgid "Please create a new Class first."
+msgstr ""
+
+#: config/schools.php.dist:91
+msgid "Problem solving behavior"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:58 lib/Forms/EditClass.php:60
+msgid "Properties"
+msgstr ""
+
+#: config/schools.php.dist:84 config/schools.php.dist:100
+#: config/schools.php.dist:106
+msgid "Reading"
+msgstr ""
+
+#: lib/Forms/DeleteClass.php:43
+#, php-format
+msgid ""
+"Really delete the class \"%s\"? This cannot be undone and all data on this "
+"class will be permanently removed."
+msgstr ""
+
+#: templates/search/criteria.inc:39
+msgid "Reset to Defaults"
+msgstr ""
+
+#: config/schools.php.dist:55
+msgid "Sample school"
+msgstr ""
+
+#: lib/Forms/Entry.php:166 lib/Forms/EditClass.php:131 templates/panel.inc:72
+msgid "Save"
+msgstr ""
+
+#: lib/Forms/EditClass.php:76
+msgid "School"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:72 lib/Forms/EditClass.php:69
+msgid "School Specific Settings"
+msgstr ""
+
+#: config/schools.php.dist:77
+msgid "Schoolhouse 1"
+msgstr ""
+
+#: config/schools.php.dist:78
+msgid "Schoolhouse 2"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:79
+msgid "Schools"
+msgstr ""
+
+#: search.php:119 lib/Block/tree_menu.php:48 templates/list/header.inc:3
+#: templates/search/header.inc:4 templates/search/criteria.inc:38
+#: config/prefs.php.dist:37
+msgid "Search"
+msgstr ""
+
+#: templates/search/criteria.inc:9
+msgid "Search Criterias"
+msgstr ""
+
+#: templates/search/header.inc:3
+msgid "Search Result"
+msgstr ""
+
+#: templates/panel.inc:39
+msgid "Search for Classes:"
+msgstr ""
+
+#: templates/search/criteria.inc:13
+msgid "Search in"
+msgstr ""
+
+#: list.php:90
+#, php-format
+msgid "Search: Results for \"%s\""
+msgstr ""
+
+#: templates/data/export.inc:26
+msgid "Select a student or the whole class to export:"
+msgstr ""
+
+#: templates/data/export.inc:17
+msgid "Select the class to export from:"
+msgstr ""
+
+#: config/prefs.php.dist:56
+msgid "Select the columns that should be shown in the class list view:"
+msgstr ""
+
+#: config/prefs.php.dist:95
+msgid "Select the columns that should be shown in the student list view:"
+msgstr ""
+
+#: templates/data/export.inc:9
+msgid "Select the export format:"
+msgstr ""
+
+#: config/prefs.php.dist:38
+msgid "Select the view to display after login:"
+msgstr ""
+
+#: templates/list/headers.inc:76 config/prefs.php.dist:53
+#: config/prefs.php.dist:69
+msgid "Semester"
+msgstr ""
+
+#: templates/list/headers.inc:52 config/prefs.php.dist:51
+#: config/prefs.php.dist:66
+msgid "Semester End"
+msgstr ""
+
+#: templates/list/headers.inc:48 config/prefs.php.dist:50
+#: config/prefs.php.dist:65
+msgid "Semester Start"
+msgstr ""
+
+#: templates/panel.inc:63
+msgid "Shared Classes:"
+msgstr ""
+
+#: templates/list/students.inc:13
+#, php-format
+msgid "Show \"%s\""
+msgstr ""
+
+#: config/prefs.php.dist:181
+msgid "Show class list options panel?"
+msgstr ""
+
+#: config/prefs.php.dist:190
+msgid "Show students in the class list?"
+msgstr ""
+
+#: templates/panel.inc:44
+msgid "Show students?"
+msgstr ""
+
+#: config/schools.php.dist:89
+msgid "Skills"
+msgstr ""
+
+#: list.php:54
+msgid "Skoli needs an applications who provides contacts (e.g. turba)."
+msgstr ""
+
+#: templates/list/headers.inc:68
+msgid "Sort by Absences"
+msgstr ""
+
+#: templates/list/headers.inc:84
+msgid "Sort by Category"
+msgstr ""
+
+#: templates/search/headers.inc:34
+msgid "Sort by Class"
+msgstr ""
+
+#: templates/search/headers.inc:40
+msgid "Sort by Date"
+msgstr ""
+
+#: templates/list/headers.inc:72
+msgid "Sort by Grade"
+msgstr ""
+
+#: templates/list/headers.inc:60
+msgid "Sort by Last Entry"
+msgstr ""
+
+#: templates/list/headers.inc:80
+msgid "Sort by Location"
+msgstr ""
+
+#: templates/list/headers.inc:64
+msgid "Sort by Mark"
+msgstr ""
+
+#: templates/list/headers.inc:56
+msgid "Sort by Name"
+msgstr ""
+
+#: templates/list/headers.inc:76
+msgid "Sort by Semester"
+msgstr ""
+
+#: templates/list/headers.inc:52
+msgid "Sort by Semester End Date"
+msgstr ""
+
+#: templates/list/headers.inc:48
+msgid "Sort by Semester Start Date"
+msgstr ""
+
+#: templates/search/headers.inc:37
+msgid "Sort by Student Name"
+msgstr ""
+
+#: templates/search/headers.inc:43
+msgid "Sort by Type"
+msgstr ""
+
+#: config/prefs.php.dist:72
+msgid "Sort classes by:"
+msgstr ""
+
+#: config/prefs.php.dist:83
+msgid "Sort direction for classes:"
+msgstr ""
+
+#: config/prefs.php.dist:119
+msgid "Sort direction for students:"
+msgstr ""
+
+#: config/prefs.php.dist:108
+msgid "Sort students by:"
+msgstr ""
+
+#: config/schools.php.dist:95
+msgid "Sport"
+msgstr ""
+
+#: lib/Forms/Entry.php:78 lib/Forms/Entry.php:80
+#: templates/search/headers.inc:37
+msgid "Student"
+msgstr ""
+
+#: lib/Forms/CreateClass.php:95 lib/Forms/CreateClass.php:108
+#: lib/Forms/CreateClass.php:141 lib/Forms/EditClass.php:83
+#: lib/Forms/EditClass.php:96 lib/Forms/EditClass.php:129
+msgid "Students"
+msgstr ""
+
+#: templates/data/export.inc:12
+msgid "Tab separated values (TSV)"
+msgstr ""
+
+#: config/schools.php.dist:99 config/schools.php.dist:105
+msgid "Talking"
+msgstr ""
+
+#: config/schools.php.dist:114
+msgid "Teamwork and autonomy"
+msgstr ""
+
+#: lib/Driver.php:38
+msgid "The School backend is not currently available."
+msgstr ""
+
+#: lib/Driver.php:87
+#, php-format
+msgid "The School backend is not currently available: %s"
+msgstr ""
+
+#: classes/create.php:38
+#, php-format
+msgid "The class \"%s\" has been created."
+msgstr ""
+
+#: classes/delete.php:43
+#, php-format
+msgid "The class \"%s\" has been deleted."
+msgstr ""
+
+#: classes/edit.php:50
+#, php-format
+msgid "The class \"%s\" has been renamed to \"%s\"."
+msgstr ""
+
+#: classes/edit.php:52
+#, php-format
+msgid "The class \"%s\" has been saved."
+msgstr ""
+
+#: entry.php:80
+#, php-format
+msgid "The entry for \"%s\" has been deleted."
+msgstr ""
+
+#: entry.php:66
+#, php-format
+msgid "The entry for \"%s\" has been saved."
+msgstr ""
+
+#: add.php:33
+#, php-format
+msgid "The new entry for \"%s\" has been added."
+msgstr ""
+
+#: lib/Forms/CreateClass.php:152
+msgid ""
+"The substitutions %c, %g or %s will be replaced automatically by the class, "
+"grade respectively semester name."
+msgstr ""
+
+#: templates/list/empty.inc:2
+msgid "There are no classes matching the current criteria."
+msgstr ""
+
+#: templates/search/empty.inc:2
+msgid "There are no entries matching the current criteria."
+msgstr ""
+
+#: entry.php:78
+#, php-format
+msgid "There was an error deleting this entry: %s"
+msgstr ""
+
+#: data.php:158
+msgid "There were no entries to export."
+msgstr ""
+
+#: index.php:20
+msgid "This file defines templates for new classes."
+msgstr ""
+
+#: lib/Forms/Entry.php:116
+msgid "Title"
+msgstr ""
+
+#: lib/Forms/Entry.php:108 templates/search/headers.inc:43
+msgid "Type"
+msgstr ""
+
+#: lib/Forms/DeleteClass.php:63
+#, php-format
+msgid "Unable to delete \"%s\": %s"
+msgstr ""
+
+#: lib/Driver.php:90
+#, php-format
+msgid "Unable to load the definition of %s."
+msgstr ""
+
+#: lib/Forms/EditClass.php:171
+#, php-format
+msgid "Unable to save class \"%s\": %s"
+msgstr ""
+
+#: lib/Skoli.php:753 lib/Skoli.php:754 lib/Skoli.php:768 lib/Skoli.php:769
+#: templates/list/classes.inc:46
+msgid "Unfiled"
+msgstr ""
+
+#: lib/Forms/Entry.php:37
+msgid "Update Entry"
+msgstr ""
+
+#: entry.php:89
+msgid "View"
+msgstr ""
+
+#: templates/list/students.inc:27
+#, php-format
+msgid "View Entries for \"%s\""
+msgstr ""
+
+#: templates/list/classes.inc:25
+#, php-format
+msgid "View Entries in \"%s\""
+msgstr ""
+
+#: templates/search/entries.inc:21
+msgid "View Entry"
+msgstr ""
+
+#: lib/Forms/Entry.php:134
+msgid "Weight"
+msgstr ""
+
+#: config/prefs.php.dist:140
+msgid "When a new class is created should we also create a new contact list?"
+msgstr ""
+
+#: data.php:39
+msgid "Whole class"
+msgstr ""
+
+#: config/schools.php.dist:85 config/schools.php.dist:101
+#: config/schools.php.dist:107
+msgid "Writing"
+msgstr ""
+
+#: classes/edit.php:28
+msgid "You are not allowed to change this class."
+msgstr ""
+
+#: classes/delete.php:30
+msgid "You are not allowed to delete this class."
+msgstr ""
+
+#: entry.php:36
+msgid "You are not allowed to view this entry."
+msgstr ""
+
+#: classes/create.php:24
+msgid "You don't have access to any valid addressbook."
+msgstr ""
+
+#: templates/panel.inc:49
+msgid "[Manage Classes]"
+msgstr ""
+
+#: lib/Skoli.php:507
+msgid "_Export"
+msgstr ""
+
+#: lib/Skoli.php:499
+msgid "_New Entry"
+msgstr ""
+
+#: lib/Skoli.php:503
+msgid "_Search"
+msgstr ""
+
+#: templates/search/criteria.inc:21
+msgid "and"
+msgstr ""
+
+#: templates/search/criteria.inc:35
+msgid "and for Entries with:"
+msgstr ""
+
+#: data.php:165 templates/data/export.inc:1
+msgid "class.csv"
+msgstr ""
+
+#: data.php:170
+msgid "class.tsv"
+msgstr ""
+
+#: templates/search/criteria.inc:25
+msgid "for"
+msgstr ""
+
+#: lib/Block/tree_menu.php:39
+#, php-format
+msgid "in %s"
+msgstr ""
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/pref_api.php,v 0.1 $
+ *
+ * Copyright 2006-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+
+@define('HORDE_BASE', dirname(dirname(__FILE__)));
+require_once HORDE_BASE . '/lib/core.php';
+
+$registry = &Registry::singleton();
+
+/* Which application. */
+$app = Util::getFormData('app');
+if (!$app) {
+ echo '<ul id="app">';
+ foreach ($registry->listApps() as $app) {
+ echo '<li>' . htmlspecialchars($app) . '</li>';
+ }
+ echo '</ul>';
+ exit;
+}
+
+/* Load $app's base environment, but don't request that the app perform
+ * authentication beyond Horde's. */
+$authentication = 'none';
+$appbase = $registry->get('fileroot', $app);
+require_once $appbase . '/lib/base.php';
+
+/* Which preference. */
+$pref = Util::getFormData('pref');
+if (!$pref) {
+ $_prefs = array();
+ if (is_callable(array('Horde', 'loadConfiguration'))) {
+ $result = Horde::loadConfiguration('prefs.php', array('_prefs'), $app);
+ if (is_a($result, 'PEAR_Error')) {
+ exit;
+ }
+ extract($result);
+ } elseif (file_exists($appbase . '/config/prefs.php')) {
+ require $appbase . '/config/prefs.php';
+ }
+
+ echo '<ul id="pref">';
+ foreach ($_prefs as $pref => $params) {
+ switch ($params['type']) {
+ case 'special':
+ case 'link':
+ break;
+
+ default:
+ echo '<li preftype="' . htmlspecialchars($params['type']) . '">' . htmlspecialchars($pref) . '</li>';
+ }
+ }
+ echo '</ul>';
+}
+
+/* Which action. */
+if (Util::getPost('pref') == $pref) {
+ /* POST for saving a pref. */
+ $prefs->setValue($pref, Util::getPost('value'));
+}
+
+/* GET returns the current value, POST returns the new value. */
+header('Content-type: text/plain');
+echo $prefs->getValue($pref);
--- /dev/null
+Deny from all
--- /dev/null
+-- $Horde: skoli/scripts/sql/skoli.sql,v 0.1 $
+
+CREATE TABLE skoli_classes_students (
+ class_id VARCHAR(255) NOT NULL,
+ student_id VARCHAR(255) NOT NULL
+);
+
+CREATE INDEX skoli_classlist_idx ON skoli_classes_students (class_id);
+CREATE INDEX skoli_studentlist_idx ON skoli_classes_students (student_id);
+
+CREATE TABLE skoli_objects (
+ object_id VARCHAR(32) NOT NULL,
+ object_owner VARCHAR(255) NOT NULL,
+ object_uid VARCHAR(255) NOT NULL,
+ class_id VARCHAR(255) NOT NULL,
+ student_id VARCHAR(255) NOT NULL,
+ object_time INT NOT NULL,
+ object_type VARCHAR(255) NOT NULL,
+ PRIMARY KEY (object_id)
+);
+
+CREATE INDEX skoli_objectlist_idx ON skoli_objects (object_owner);
+CREATE INDEX skoli_uid_idx ON skoli_objects (object_uid);
+CREATE INDEX skoli_classlist_idx ON skoli_objects (class_id);
+CREATE INDEX skoli_studentlist_idx ON skoli_objects (student_id);
+
+CREATE TABLE skoli_object_attributes (
+ object_id VARCHAR(32) NOT NULL,
+ attr_name VARCHAR(50) NOT NULL,
+ attr_value VARCHAR(255),
+ PRIMARY KEY (object_id, attr_name)
+);
+CREATE INDEX skoli_object_attributes_object_idx ON skoli_object_attributes (object_id);
+
+CREATE TABLE skoli_shares (
+ share_id INT NOT NULL,
+ share_name VARCHAR(255) NOT NULL,
+ share_owner VARCHAR(32) NOT NULL,
+ share_flags SMALLINT NOT NULL DEFAULT 0,
+ perm_creator SMALLINT NOT NULL DEFAULT 0,
+ perm_default SMALLINT NOT NULL DEFAULT 0,
+ perm_guest SMALLINT NOT NULL DEFAULT 0,
+ attribute_name VARCHAR(255) NOT NULL,
+ attribute_desc VARCHAR(255),
+ attribute_school VARCHAR(255) NOT NULL,
+ attribute_grade VARCHAR(255),
+ attribute_semester VARCHAR(255),
+ attribute_start INT NOT NULL,
+ attribute_end INT NOT NULL,
+ attribute_category VARCHAR(255) NULL,
+ attribute_location VARCHAR(255),
+ attribute_marks VARCHAR(255),
+ attribute_address_book VARCHAR(255) NOT NULL,
+ PRIMARY KEY (share_id)
+);
+
+CREATE INDEX skoli_shares_share_name_idx ON skoli_shares (share_name);
+CREATE INDEX skoli_shares_share_owner_idx ON skoli_shares (share_owner);
+CREATE INDEX skoli_shares_perm_creator_idx ON skoli_shares (perm_creator);
+CREATE INDEX skoli_shares_perm_default_idx ON skoli_shares (perm_default);
+CREATE INDEX skoli_shares_perm_guest_idx ON skoli_shares (perm_guest);
+CREATE INDEX skoli_shares_attribute_category_idx ON skoli_shares (attribute_category);
+CREATE INDEX skoli_shares_attribute_address_book_idx ON skoli_shares (attribute_address_book);
+
+CREATE TABLE skoli_shares_groups (
+ share_id INT NOT NULL,
+ group_uid VARCHAR(255) NOT NULL,
+ perm SMALLINT NOT NULL
+);
+
+CREATE INDEX skoli_shares_groups_share_id_idx ON skoli_shares_groups (share_id);
+CREATE INDEX skoli_shares_groups_group_uid_idx ON skoli_shares_groups (group_uid);
+CREATE INDEX skoli_shares_groups_perm_idx ON skoli_shares_groups (perm);
+
+CREATE TABLE skoli_shares_users (
+ share_id INT NOT NULL,
+ user_uid VARCHAR(255) NOT NULL,
+ perm SMALLINT NOT NULL
+);
+
+CREATE INDEX skoli_shares_users_share_id_idx ON skoli_shares_users (share_id);
+CREATE INDEX skoli_shares_users_user_uid_idx ON skoli_shares_users (user_uid);
+CREATE INDEX skoli_shares_users_perm_idx ON skoli_shares_users (perm);
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/search.php,v 0.1 $
+ *
+ * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author Martin Blumenthal <tinu@humbapa.ch>
+ */
+
+require_once dirname(__FILE__) . '/lib/base.php';
+
+$classes = Skoli::listClasses();
+
+/* If there are no valid classes, abort. */
+if (count($classes) == 0) {
+ $notification->push(_("No classes are currently available. Searching is disabled."), 'horde.error');
+ require SKOLI_TEMPLATES . '/common-header.inc';
+ require SKOLI_TEMPLATES . '/menu.inc';
+ require $registry->get('templates', 'horde') . '/common-footer.inc';
+ exit;
+}
+
+$actionID = Util::getFormData('actionID');
+
+if (!isset($_SESSION['skoli'])) {
+ $_SESSION['skoli'] = array();
+}
+
+if (($classid = Util::getFormData('class')) !== null) {
+ $_SESSION['skoli']['search_classid'] = $classid;
+} else if (isset($_SESSION['skoli']['search_classid'])) {
+ $classid = $_SESSION['skoli']['search_classid'];
+}
+if (($studentid = Util::getFormData('student')) !== null) {
+ $_SESSION['skoli']['search_studentid'] = $studentid;
+} else if (isset($_SESSION['skoli']['search_studentid'])) {
+ $studentid = $_SESSION['skoli']['search_studentid'];
+}
+if (($type = Util::getFormData('type')) !== null) {
+ $_SESSION['skoli']['search_type'] = $type;
+} else if (isset($_SESSION['skoli']['search_type'])) {
+ $type = $_SESSION['skoli']['search_type'];
+}
+
+$search = Util::getFormData('stext');
+
+/* Sort out the sorting values */
+$sortby = Util::getFormData('sortby');
+$sortdir = Util::getFormData('sortdir');
+if ($sortby === null) {
+ $sortby = SKOLI_SORT_CLASS;
+} else if ($sortby == Util::getFormData('sortby')) {
+ $sortdir = !$sortdir;
+}
+if ($sortdir === null) {
+ $sortdir = SKOLI_SORT_ASCEND;
+}
+
+$class_options = array();
+if (count($classes) > 1) {
+ $class_options[] = '<option value="all">' .
+ htmlspecialchars(_("All classes")) . "</option>\n";
+}
+foreach ($classes as $key=>$class) {
+ $class_options[] = '<option value="' . htmlspecialchars($key) . '"' . ($classid == $key ? ' selected="selected"' : '') . '>' .
+ htmlspecialchars($class->get('name')) . "</option>\n";
+}
+
+$student_options = array();
+$student_options[] = '<option value="all">' .
+ htmlspecialchars(_("All students")) . "</option>\n";
+if ($classid == '' || $classid == 'all') {
+ $studentslist = Skoli::listStudents(null, SKOLI_SORT_NAME, SKOLI_SORT_ASCEND);
+ $students = array();
+ foreach ($studentslist as $val) {
+ $students = array_merge($students, $val['_students']);
+ }
+} else {
+ $studentslist = current(Skoli::listStudents($classid, SKOLI_SORT_NAME, SKOLI_SORT_ASCEND));
+ $students = $studentslist['_students'];
+}
+$foundstudent = false;
+foreach ($students as $address) {
+ if ($studentid == $address['student_id']) {
+ $foundstudent = true;
+ }
+ $student_options[] = '<option value="' . htmlspecialchars($address['student_id']) . '"' . ($studentid == $address['student_id'] ? ' selected="selected"' : '') . '">' .
+ htmlspecialchars($address[$conf['addresses']['name_field']]) . "</option>\n";
+}
+if (!$foundstudent && $studentid != 'all') {
+ $actionID = '';
+ $studentid = '';
+ $_SESSION['skoli']['search_studentid'] = $studentid;
+}
+
+$type_options = array();
+$type_options[] = '<option value="all">' .
+ htmlspecialchars(_("All Types")) . "</option>\n";
+if ($conf['objects']['allow_marks']) {
+ $type_options[] = '<option value="mark"' . ($type == 'mark' ? ' selected="selected"' : '') . '>' .
+ htmlspecialchars(_("Marks")) . "</option>\n";
+}
+if ($conf['objects']['allow_objectives']) {
+ $type_options[] = '<option value="objective"' . ($type == 'objective' ? ' selected="selected"' : '') . '>' .
+ htmlspecialchars(_("Objectives")) . "</option>\n";
+}
+if ($conf['objects']['allow_outcomes']) {
+ $type_options[] = '<option value="outcome"' . ($type == 'outcome' ? ' selected="selected"' : '') . '>' .
+ htmlspecialchars(_("Outcomes")) . "</option>\n";
+}
+if ($conf['objects']['allow_absences']) {
+ $type_options[] = '<option value="absence"' . ($type == 'absence' ? ' selected="selected"' : '') . '>' .
+ htmlspecialchars(_("Absences")) . "</option>\n";
+}
+
+$title = _("Search");
+$notification->push('document.skoli_searchform.stext.focus();', 'javascript');
+
+Horde::addScriptFile('prototype.js', 'horde', true);
+Horde::addScriptFile('QuickFinder.js', 'horde', true);
+Horde::addScriptFile('effects.js', 'horde', true);
+Horde::addScriptFile('redbox.js', 'horde', true);
+require SKOLI_TEMPLATES . '/common-header.inc';
+require SKOLI_TEMPLATES . '/menu.inc';
+reset($classes);
+require SKOLI_TEMPLATES . '/search/criteria.inc';
+
+if ($actionID == 'search') {
+ $params = array($search);
+ $list = Skoli::listEntries($classid == 'all' ? null : $classid, $studentid == 'all' ? null : $studentid, $type == 'all' ? null : $type, $params, $sortby, $sortdir);
+
+ $dynamic_sort = false;
+ $params = array('actionID' => 'search');
+ $baseurl = Util::addParameter('search.php', $params);
+ echo '<div id="page">';
+ require SKOLI_TEMPLATES . '/search/header.inc';
+ if (count($list) > 0) {
+ require SKOLI_TEMPLATES . '/search/headers.inc';
+ foreach ($list as $entry) {
+ $style = 'linedRow';
+ $details = '';
+
+ switch ($entry['type']) {
+ case 'mark':
+ $details = $entry['subject'] . ': ' . $entry['mark'] .
+ ($classes[$entry['classid']]->get('marks') == 'percent' ? '%' : '') .
+ ' (' . $entry['weight'] . '), ' . $entry['title'];
+ break;
+
+ case 'objective':
+ $details = $entry['category'] . ' (' . $entry['subject'] . '): ' . $entry['objective'];
+ break;
+
+ case 'outcome':
+ $details = $entry['outcome'] . ': ' .
+ (isset($entry['completed']) && $entry['completed'] != '' ? _("Completed") : _("Open")) .
+ (isset($entry['comment']) && $entry['comment'] != '' ? ', ' . $entry['comment'] : '');
+ break;
+
+ case 'absence':
+ $details = (isset($entry['excused']) && $entry['excused'] != '' ? _("Excused") : _("Not excused")) .
+ ': ' . $entry['absence'] .
+ (isset($entry['comment']) && $entry['comment'] != '' ? ', ' . $entry['comment'] : '');
+ break;
+ }
+ $detailswrapped = String::wordwrap($details, $prefs->getValue('entry_details_wrap'), '<br />', true);
+ $entry['details'] = current(explode('<br />', $detailswrapped));
+ require SKOLI_TEMPLATES . '/search/entries.inc';
+ }
+
+ require SKOLI_TEMPLATES . '/search/footers.inc';
+
+ if ($dynamic_sort) {
+ Horde::addScriptFile('tables.js', 'horde', true);
+ }
+ } else {
+ require SKOLI_TEMPLATES . '/search/empty.inc';
+ }
+}
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
--- /dev/null
+<h1 class="header">
+ <?php echo _("Manage Classes") ?>
+</h1>
+
+<div id="class-list-buttons">
+ <form method="get" action="create.php">
+<?php echo Util::formInput() ?>
+ <input type="submit" class="button" value="<?php echo _("Create a new Class") ?>" />
+ </form>
+</div>
+
+<?php if (count($sorted_classes) > 0): ?>
+<table summary="<?php echo _("Class List") ?>" cellspacing="0" id="class-list" class="striped sortable">
+ <thead>
+ <tr>
+ <th class="sortdown"><?php echo _("Class") ?></th>
+ <th class="class-list-icon nosort" colspan="<?php echo empty($conf['share']['no_sharing']) ? 3 : 2 ?>"> </th>
+ </tr>
+ </thead>
+
+ <tbody>
+<?php foreach (array_keys($sorted_classes) as $class_id): ?>
+ <?php $class = $classes[$class_id] ?>
+ <tr>
+ <td><?php echo htmlspecialchars($class->get('name')) ?></td>
+ <td><a href="<?php echo Util::addParameter($edit_url_base, 'c', $class->getName()) ?>" title="<?php echo _("Edit") ?>"><?php echo $edit_img ?></a></td>
+<?php if (empty($conf['share']['no_sharing'])): ?>
+ <td><a onclick="return !popup(this.href);" href="<?php echo Util::addParameter($perms_url_base, 'share', $class->getName()) ?>" target="_blank" title="<?php echo _("Change Permissions") ?>"><?php echo $perms_img ?></a></td>
+<?php endif; ?>
+ <td><a href="<?php echo Util::addParameter($delete_url_base, 'c', $class->getName()) ?>" title="<?php echo _("Delete") ?>"><?php echo $delete_img ?></a></td>
+ </tr>
+<?php endforeach; ?>
+ </tbody>
+</table>
+<?php endif; ?>
--- /dev/null
+<?php
+if (isset($language)) {
+ header('Content-type: text/html; charset=' . NLS::getCharset());
+ header('Vary: Accept-Language');
+}
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
+<!-- Skoli: Copyright 2004-2008 The Horde Project. Skoli is under a Horde license. -->
+<!-- Horde Project: http://www.horde.org/ | Skoli: http://www.horde.org/horde/ -->
+<!-- Horde Licenses: http://www.horde.org/licenses/ -->
+<?php echo !empty($language) ? '<html lang="' . strtr($language, '_', '-') . '">' : '<html>' ?>
+<head>
+<?php
+
+$page_title = $registry->get('name');
+if (!empty($title)) $page_title .= ' :: ' . $title;
+if (!empty($refresh_time) && ($refresh_time > 0) && !empty($refresh_url)) {
+ echo "<meta http-equiv=\"refresh\" content=\"$refresh_time;url=$refresh_url\">\n";
+}
+
+Horde::includeScriptFiles();
+
+$bc = Util::nonInputVar('bodyClass');
+if ($prefs->getValue('show_panel')) {
+ if ($bc) {
+ $bc .= ' ';
+ }
+ $bc .= 'rightPanel';
+}
+
+?>
+<title><?php echo htmlspecialchars($page_title) ?></title>
+<link href="<?php echo $GLOBALS['registry']->getImageDir()?>/favicon.ico" rel="SHORTCUT ICON" />
+<?php echo Horde::stylesheetLink('skoli') ?>
+<link href="<?php echo Horde::applicationUrl('themes/categoryCSS.php') ?>" rel="stylesheet" type="text/css" />
+</head>
+
+<body<?php if ($bc) echo ' class="' . $bc . '"' ?><?php if ($bi = Util::nonInputVar('bodyId')) echo ' id="' . $bi . '"'; ?>>
--- /dev/null
+<form method="post" name="skoli_exportform" id="skoli_exportform" action="<?php echo Horde::downloadUrl(_("class.csv"), null, Horde::applicationUrl('data.php')) ?>">
+<input type="hidden" name="actionID" value="export" />
+
+<h1 class="header">
+ <?php echo _("Export Classes") ?>
+</h1>
+
+<div class="item" style="padding:1em">
+ <?php echo Horde::label('exportID', _("Select the export format:")) ?><br />
+ <select name="exportID" id="exportID">
+ <option value="<?php echo EXPORT_CSV ?>"<?php echo Util::getFormData('exportID') == EXPORT_CSV ? ' selected="selected"' : '' ?>><?php echo _("Comma separated values (CSV)") ?></option>
+ <option value="<?php echo EXPORT_TSV ?>"<?php echo Util::getFormData('exportID') == EXPORT_TSV ? ' selected="selected"' : '' ?>><?php echo _("Tab separated values (TSV)") ?></option>
+ </select><br />
+
+<?php if (count($classes) > 1): ?>
+ <br />
+ <?php echo Horde::label('class', _("Select the class to export from:")) ?><br />
+ <select name="class" id="class" onchange="if (this.value) { document.skoli_exportform.actionID.value=''; RedBox.loading(); document.skoli_exportform.submit() }">
+ <?php echo implode('', $class_options) ?>
+ </select><br />
+<?php else: ?>
+ <input type="hidden" name="class" value="<?php echo key($classes) ?>" />
+<?php endif; ?>
+ <br />
+
+ <?php echo Horde::label('student', _("Select a student or the whole class to export:")) ?><br />
+ <select name="student" id="student">
+ <?php echo implode('', $student_options) ?>
+ </select><br />
+ <br />
+
+ <input type="submit" value="<?php echo _("Export") ?>" class="button" />
+</div>
+</form>
--- /dev/null
+<form action="entry.php" method="post">
+<?php echo Util::formInput() ?>
+<input type="hidden" name="actionID" value="delete" />
+<input type="hidden" name="entry" value="<?php echo $entry['object_id'] ?>" />
+<input type="hidden" name="view" value="<?php echo $viewName ?>" />
+<div class="headerbox" style="padding: 8px">
+ <p><?php echo _("Permanently delete this entry?") ?></p>
+ <input type="submit" class="button" name="delete" value="<?php echo _("Delete") ?>" />
+</div>
+</form>
--- /dev/null
+<tr class="<?php echo $style ?>">
+ <td>
+ <?php
+ if ($class['_edit']) {
+ $label = _("New Entry");
+ echo Horde::link(Util::addParameter(Horde::applicationUrl('add.php'), 'class', $class['_id']), $label) . Horde::img('add.png', $label, null, $registry->getImageDir('skoli')) . '</a>';
+ }
+ ?>
+ </td>
+ <td>
+ <?php
+ if ($class['_edit']) {
+ $label = sprintf(_("Edit \"%s\""), $class['name']);
+ echo Horde::link(Util::addParameter(Horde::applicationUrl('classes/edit.php'), 'c', $class['_id']), $label) . Horde::img('edit.png', $label, null, $registry->getImageDir('horde')) . '</a>';
+ }
+ ?>
+ </td>
+<?php if (in_array('semesterstart', $class_columns)): ?>
+ <td><?php echo strftime($dateFormat, $class['start']) ?> </td>
+<?php endif; if (in_array('semesterend', $class_columns)): ?>
+ <td><?php echo strftime($dateFormat, $class['end']) ?> </td>
+<?php endif; ?>
+ <td>
+ <?php
+ $label = sprintf(_("View Entries in \"%s\""), $class['name']);
+ $params = array(
+ 'actionID' => 'search',
+ 'class' => $class['_id']
+ );
+ echo Horde::link(Util::addParameter(Horde::applicationUrl('search.php'), $params), $label) . htmlspecialchars($class['name']) . '</a>';
+ ?>
+ </td>
+<?php if (in_array('lastentry', $student_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('summarks', $student_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('sumabsences', $student_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('grade', $class_columns)): ?>
+ <td><?php echo htmlspecialchars($class['grade']) ?> </td>
+<?php endif; if (in_array('semester', $class_columns)): ?>
+ <td><?php echo htmlspecialchars($class['semester']) ?> </td>
+<?php endif; if (in_array('location', $class_columns)): ?>
+ <td><?php echo htmlspecialchars($class['location']) ?> </td>
+<?php endif; if (in_array('category', $class_columns)): ?>
+ <td class="category<?php echo md5($class['category']) ?>"><?php echo isset($class['category']) ? htmlspecialchars($class['category']) : _("Unfiled") ?></td>
+<?php endif; ?>
+</tr>
--- /dev/null
+<p class="text">
+ <em><?php echo _("There are no classes matching the current criteria.") ?></em>
+<p>
--- /dev/null
+</tbody>
+</table>
+<div id="classes_empty">
+ <?php echo _("No classes match") ?>
+</div>
+</div>
--- /dev/null
+<div class="header leftAlign">
+ <?php echo htmlspecialchars($title) ?>
+ <a id="quicksearchL" href="<?php echo Horde::applicationUrl('search.php') ?>" title="<?php echo _("Search") ?>" onclick="$('quicksearchL').hide(); $('quicksearch').show(); $('quicksearchT').focus(); return false;"><?php echo Horde::img('search.png', _("Search"), '', $registry->getImageDir('horde')) ?></a>
+ <div id="quicksearch" style="display:none">
+ <input type="text" name="quicksearchT" id="quicksearchT" for="classes-body" empty="classes_empty" />
+ <small>
+ <a title="<?php echo _("Close Search") ?>" href="#" onclick="$('quicksearch').hide(); $('quicksearchT').value = ''; QuickFinder.filter($('quicksearchT')); $('quicksearchL').show(); return false;">X</a>
+ </small>
+ </div>
+</div>
--- /dev/null
+<?php $sortdirclass_class = $sortdir_class ? 'sortup' : 'sortdown' ?>
+<?php $sortdirclass_student = $sortdir_student ? 'sortup' : 'sortdown' ?>
+
+<script type="text/javascript">
+
+var PREFS_UPDATE_TIMEOUT;
+
+function table_sortCallback(tableId, column, sortDown)
+{
+ if (typeof PREFS_UPDATE_TIMEOUT != "undefined") {
+ window.clearTimeout(PREFS_UPDATE_TIMEOUT);
+ }
+
+ PREFS_UPDATE_TIMEOUT = window.setTimeout('doPrefsUpdate("' + column + '", "' + sortDown + '")', 300);
+}
+
+function doPrefsUpdate(column, sortDown)
+{
+ baseurl = '<?php echo Horde::applicationUrl('pref_api.php', true) ?>';
+ try {
+ column = column.substring(1);
+ prefscope = 'class';
+ if (column == 'lastentry' || column == 'comment') {
+ prefscope = 'student';
+ }
+ new Ajax.Request(baseurl, { parameters: { app: 'skoli', pref: 'sortby_' + prefscope, value: encodeURIComponent(column) } });
+ new Ajax.Request(baseurl, { parameters: { app: 'skoli', pref: 'sortdir_' + prefscope, value: encodeURIComponent(sortDown) } });
+ if (column == 'name') {
+ new Ajax.Request(baseurl, { parameters: { app: 'skoli', pref: 'sortby_student', value: encodeURIComponent(column) } });
+ new Ajax.Request(baseurl, { parameters: { app: 'skoli', pref: 'sortdir_student', value: encodeURIComponent(sortDown) } });
+ }
+ } catch (e) {}
+}
+
+</script>
+
+<table id="classes" cellspacing="0" class="sortable nowrap">
+<thead>
+ <tr class="item leftAlign">
+ <th width="2%" class="nosort">
+ <?php echo Horde::img('add.png', _("New Entry"), null, $registry->getImageDir('skoli')) ?>
+ </th>
+ <th width="2%" class="nosort">
+ <?php echo Horde::img('edit.png', _("Edit Class"), null, $registry->getImageDir('horde')) ?>
+ </th>
+<?php if (in_array('semesterstart', $class_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_SEMESTERSTART ?>"<?php if ($sortby_class == SKOLI_SORT_SEMESTERSTART) echo ' class="' . $sortdirclass_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_SEMESTERSTART)), _("Sort by Semester Start Date"), 'sortlink', '', '', _("Semester Start")) ?>
+ </th>
+<?php endif; if (in_array('semesterend', $class_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_SEMESTEREND ?>"<?php if ($sortby_class == SKOLI_SORT_SEMESTEREND) echo ' class="' . $sortdirclass_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_SEMESTEREND)), _("Sort by Semester End Date"), 'sortlink', '', '', _("Semester End")) ?>
+ </th>
+<?php endif; ?>
+ <th id="s<?php echo SKOLI_SORT_NAME ?>"<?php if ($sortby_class == SKOLI_SORT_NAME) echo ' class="' . $sortdirclass_class . '"' ?>>
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_NAME)), _("Sort by Name"), 'sortlink', '', '', _("Name")) ?>
+ </th>
+<?php if (in_array('lastentry', $student_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_LASTENTRY ?>"<?php if ($sortby_student == SKOLI_SORT_LASTENTRY) echo ' class="' . $sortdirclass_student . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_student', SKOLI_SORT_LASTENTRY)), _("Sort by Last Entry"), 'sortlink', '', '', _("Last Entry")) ?>
+ </th>
+<?php endif; if (in_array('summarks', $student_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_SUMMARKS ?>"<?php if ($sortby_student == SKOLI_SORT_SUMMARKS) echo ' class="' . $sortdirclass_student . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_student', SKOLI_SORT_SUMMARKS)), _("Sort by Mark"), 'sortlink', '', '', _("Mark average")) ?>
+ </th>
+<?php endif; if (in_array('sumabsences', $student_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_SUMABSENCES ?>"<?php if ($sortby_student == SKOLI_SORT_SUMABSENCES) echo ' class="' . $sortdirclass_student . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_student', SKOLI_SORT_SUMABSENCES)), _("Sort by Absences"), 'sortlink', '', '', _("Absences")) ?>
+ </th>
+<?php endif; if (in_array('grade', $class_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_GRADE ?>"<?php if ($sortby_class == SKOLI_SORT_GRADE) echo ' class="' . $sortdirclass_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_GRADE)), _("Sort by Grade"), 'sortlink', '', '', _("Grade")) ?>
+ </th>
+<?php endif; if (in_array('semester', $class_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_SEMESTER ?>"<?php if ($sortby_class == SKOLI_SORT_SEMESTER) echo ' class="' . $sortdirclass_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_SEMESTER)), _("Sort by Semester"), 'sortlink', '', '', _("Semester")) ?>
+ </th>
+<?php endif; if (in_array('location', $class_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_LOCATION ?>"<?php if ($sortby_class == SKOLI_SORT_LOCATION) echo ' class="' . $sortdirclass_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_LOCATION)), _("Sort by Location"), 'sortlink', '', '', _("Location")) ?>
+ </th>
+<?php endif; if (in_array('category', $class_columns)): ?>
+ <th id="s<?php echo SKOLI_SORT_CATEGORY ?>"<?php if ($sortby_class == SKOLI_SORT_CATEGORY) echo ' class="' . $sortdirclass_class . '"' ?> width="10%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby_class', SKOLI_SORT_CATEGORY)), _("Sort by Category"), 'sortlink', '', '', _("Category")) ?>
+ <?php
+ if (Auth::getAuth() && (!$GLOBALS['prefs']->isLocked('categories') ||
+ !$GLOBALS['prefs']->isLocked('category_colors'))) {
+ $categoryUrl = Util::addParameter(Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/prefs.php'), array('app' => 'horde', 'group' => 'categories'));
+ echo ' ' . Horde::link($categoryUrl, _("Edit categories and colors"), '', '_blank', 'popup(this.href); return false;') . Horde::img('colorpicker.png', _("Edit categories and colors"), '', $GLOBALS['registry']->getImageDir('horde')) . '</a>';
+ }
+ ?>
+ </th>
+<?php endif; ?>
+ </tr>
+</thead>
+<tbody id="classes-body">
--- /dev/null
+<tr class="<?php echo $style ?>">
+ <td>
+ <?php
+ if ($class['_edit']) {
+ $label = _("New Entry");
+ echo Horde::link(Util::addParameter(Horde::applicationUrl('add.php'), array('class'=>$class['_id'], 'student'=>$student['__key'])), $label) . Horde::img('add.png', $label, null, $registry->getImageDir('skoli')) . '</a>';
+ }
+ ?>
+ </td>
+ <td>
+ <?php
+ if ($registry->hasMethod('contacts/show')) {
+ $label = sprintf(_("Show \"%s\""), $student[$conf['addresses']['name_field']]);
+ $url = $registry->link('contacts/show', array('source' => $class['address_book'],
+ 'key' => $student['__key']));
+ echo Horde::link($url, $label) . Horde::img('user.png', $label, null, $registry->getImageDir('horde')) . '</a>';
+ }
+ ?>
+ </td>
+<?php if (in_array('semesterstart', $class_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('semesterend', $class_columns)): ?>
+ <td> </td>
+<?php endif; ?>
+ <td>
+ <?php
+ $label = sprintf(_("View Entries for \"%s\""), $student[$conf['addresses']['name_field']]);
+ $params = array(
+ 'actionID' => 'search',
+ 'class' => $class['_id'],
+ 'student' => $student['__key']
+ );
+ echo $treeIcon . ' ' . Horde::link(Util::addParameter(Horde::applicationUrl('search.php'), $params), $label) . htmlspecialchars($student[$conf['addresses']['name_field']]) . '</a>';
+ ?>
+ </td>
+<?php if (in_array('lastentry', $student_columns)): ?>
+ <td><?php echo isset($student['_lastentry']) ? strftime($dateFormat, $student['_lastentry']) : '' ?> </td>
+<?php endif; if (in_array('summarks', $student_columns)): ?>
+ <td style="text-align: center;"><?php echo $student['_summarks'] != '' ? $student['_summarks'] : ' ' ?></td>
+<?php endif; if (in_array('sumabsences', $student_columns)): ?>
+ <td style="text-align: center;"><?php echo $student['_sumabsences'][2] ?></td>
+<?php endif; if (in_array('grade', $class_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('semester', $class_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('location', $class_columns)): ?>
+ <td> </td>
+<?php endif; if (in_array('category', $class_columns)): ?>
+ <td> </td>
+<?php endif; ?>
+</tr>
--- /dev/null
+<div id="menu">
+ <?php echo Skoli::getMenu('string') ?>
+</div>
+<?php $GLOBALS['notification']->notify(array('listeners' => 'status')) ?>
--- /dev/null
+<?php
+Horde::addScriptFile('prototype.js', 'horde', true);
+Horde::addScriptFile('QuickFinder.js', 'horde', true);
+
+$current_user = Auth::getAuth();
+$my_classes = array();
+$shared_classes = array();
+foreach (Skoli::listClasses() as $id => $cl) {
+ if ($cl->get('owner') == $current_user) {
+ $my_classes[$id] = $cl;
+ } else {
+ $shared_classes[$id] = $cl;
+ }
+}
+?>
+
+<div id="pageControls">
+<script type="text/javascript">
+function sbarToggle()
+{
+ var pref_value;
+ if (Element.hasClassName(document.body, 'rightPanel')) {
+ pref_value = 0;
+ Element.removeClassName(document.body, 'rightPanel');
+ } else {
+ pref_value = 1;
+ Element.addClassName(document.body, 'rightPanel');
+ }
+
+ new Ajax.Request('<?php echo Horde::applicationUrl('pref_api.php', true) ?>', { parameters: { app: 'skoli', pref: 'show_panel', value: pref_value } });
+}
+</script>
+<a id="sbarShow" href="#" onclick="sbarToggle(); return false;"><?php echo _("Classes") ?></a>
+<a id="sbarHide" href="#" onclick="sbarToggle(); return false;"><?php echo _("Classes") ?></a>
+
+<div id="pageControlsInner">
+<form action="<?php echo Horde::selfUrl() ?>" method="post">
+<?php echo Util::formInput() ?>
+ <h4><?php echo _("Search for Classes:") ?></h4>
+ <p>
+ <input id="classSearch" type="text" for="myclasses,sharedclasses" />
+ </p>
+ <p>
+ <label><input type="checkbox" class="checkbox" name="show_students" value="1"<?php echo ($prefs->getValue('show_students') ? ' checked="checked"' : '') . ' /> ' . _("Show students?") ?></label>
+ </p>
+
+<?php if (Auth::getAuth()): ?>
+ <p>
+ <a href="<?php echo Horde::applicationUrl('classes/') ?>"><?php echo _("[Manage Classes]") ?></a>
+ </p>
+<?php endif; ?>
+
+<?php if (count($my_classes)): ?>
+ <h4><?php echo _("My Classes:") ?></h4>
+ <ul id="myclasses">
+<?php foreach ($my_classes as $id => $cl): ?>
+ <li><label><input type="checkbox" class="checkbox" name="display_class[]" value="<?php echo htmlspecialchars($id) ?>"<?php echo (in_array($id, $display_classes) ? ' checked="checked"' : '') . ' /> ' . htmlspecialchars($cl->get('name')) ?></label></li>
+<?php endforeach; ?>
+ </ul>
+<?php endif; ?>
+
+<?php if (count($shared_classes)): ?>
+ <h4><?php echo _("Shared Classes:") ?></h4>
+ <ul id="sharedclasses">
+<?php foreach ($shared_classes as $id => $cl): ?>
+ <li><label><input type="checkbox" class="checkbox" name="display_class[]" value="<?php echo htmlspecialchars($id) ?>"<?php echo (in_array($id, $display_classes) ? ' checked="checked"' : '') . ' /> [' . htmlspecialchars(Auth::removeHook($cl->get('owner'))) . '] ' . htmlspecialchars($cl->get('name')) ?></label></li>
+<?php endforeach; ?>
+ </ul>
+<?php endif; ?>
+
+ <p>
+ <input type="submit" value="<?php echo _("Save") ?>" class="button" />
+ </p>
+</form>
+
+</div>
+</div>
--- /dev/null
+<?php
+/* Build the directory sources select widget. */
+
+?>
+<form name="skoli_searchform" action="search.php" method="get" onsubmit="RedBox.loading(); return true;">
+<input type="hidden" name="actionID" value="search" />
+
+<h1 class="header">
+ <?php echo _("Search Criterias") ?>
+</h1>
+
+<div class="text" style="padding:1em">
+<strong><?php echo Horde::label('class', _("Search in")) ?></strong>
+<?php if (count($classes) > 1): ?>
+ <select name="class" id="class" onchange="if (this.value) { document.skoli_searchform.actionID.value='<?php echo $actionID ?>'; RedBox.loading(); document.skoli_searchform.submit() }">
+ <?php echo implode('', $class_options) ?>
+ </select>
+<?php else: ?>
+ <strong><?php echo current($classes)->get('name') ?></strong>
+<?php endif; ?>
+<strong> <?php echo Horde::label('student', _("and")) ?></strong>
+<select name="student" id="student">
+ <?php echo implode('', $student_options) ?>
+</select>
+<strong> <?php echo Horde::label('type', _("for")) ?></strong>
+<select name="type" id="type">
+ <?php echo implode('', $type_options) ?>
+</select><br />
+
+<br />
+
+<table cellspacing="0" cellpadding="0" width="100%">
+ <tr>
+ <td>
+ <strong><?php echo Horde::label('stext', _("and for Entries with:")) ?></strong>
+ <input type="text" size="30" id="stext" name="stext" value="<?php echo htmlspecialchars($search) ?>" />
+ <td style="text-align: center;">
+ <input type="submit" class="button" name="search" value="<?php echo _("Search") ?>" />
+ <input type="reset" class="button" name="reset" value="<?php echo _("Reset to Defaults") ?>" />
+ </td>
+ </tr>
+</table>
+</div>
+</form>
--- /dev/null
+<p class="text">
+ <em><?php echo _("There are no entries matching the current criteria.") ?></em>
+<p>
--- /dev/null
+<tr class="<?php echo $style ?>">
+ <td>
+ <?php
+ if ($entry['_edit']) {
+ $label = _("Edit Entry");
+ $params = array(
+ 'view' => 'EditEntry',
+ 'entry' => $entry['_id']
+ );
+ echo Horde::link(Util::addParameter(Horde::applicationUrl('entry.php'), $params), $label) . Horde::img('edit.png', $label, null, $registry->getImageDir('horde')) . '</a>';
+ }
+ ?>
+ </td>
+ <td><?php echo htmlspecialchars($entry['class']) ?> </td>
+ <td>
+ <?php
+ $params = array(
+ 'view' => 'Entry',
+ 'entry' => $entry['_id']
+ );
+ echo Horde::link(Util::addParameter(Horde::applicationUrl('entry.php'), $params), _("View Entry")) . htmlspecialchars($entry['student']) . '</a> ';
+ ?>
+ </td>
+ <td><?php echo htmlspecialchars($entry['date']) ?> </td>
+ <td><?php echo htmlspecialchars($entry['typename']) ?> </td>
+ <td><?php echo htmlspecialchars($entry['details']) ?> </td>
+</tr>
--- /dev/null
+</tbody>
+</table>
+<div id="entries_empty">
+ <?php echo _("No entries match") ?>
+</div>
+</div>
--- /dev/null
+<br />
+<div class="header leftAlign">
+ <?php echo htmlspecialchars(_("Search Result")) ?>
+ <a id="quicksearchL" href="<?php echo Horde::applicationUrl('search.php') ?>" title="<?php echo _("Search") ?>" onclick="$('quicksearchL').hide(); $('quicksearch').show(); $('quicksearchT').focus(); return false;"><?php echo Horde::img('search.png', _("Search"), '', $registry->getImageDir('horde')) ?></a>
+ <div id="quicksearch" style="display:none">
+ <input type="text" name="quicksearchT" id="quicksearchT" for="entries-body" empty="entries_empty" />
+ <small>
+ <a title="<?php echo _("Close Search") ?>" href="#" onclick="$('quicksearch').hide(); $('quicksearchT').value = ''; QuickFinder.filter($('quicksearchT')); $('quicksearchL').show(); return false;">X</a>
+ </small>
+ </div>
+</div>
--- /dev/null
+<?php $sortdir_class = $sortdir ? 'sortup' : 'sortdown' ?>
+
+<script type="text/javascript">
+
+var PREFS_UPDATE_TIMEOUT;
+
+function table_sortCallback(tableId, column, sortDown)
+{
+ if (typeof PREFS_UPDATE_TIMEOUT != "undefined") {
+ window.clearTimeout(PREFS_UPDATE_TIMEOUT);
+ }
+
+ PREFS_UPDATE_TIMEOUT = window.setTimeout('doPrefsUpdate("' + column + '", "' + sortDown + '")', 300);
+}
+
+function doPrefsUpdate(column, sortDown)
+{
+ baseurl = '<?php echo Horde::applicationUrl('pref_api.php', true) ?>';
+ try {
+ column = column.substring(1);
+ new Ajax.Request(baseurl, { parameters: { app: 'skoli', pref: 'sortby', value: encodeURIComponent(column) } });
+ } catch (e) {}
+}
+
+</script>
+
+<table id="entries" cellspacing="0" class="sortable nowrap">
+<thead>
+ <tr class="item leftAlign">
+ <th width="2%" class="nosort">
+ <?php echo Horde::img('edit.png', _("Edit Class"), null, $registry->getImageDir('horde')) ?>
+ </th>
+ <th id="s<?php echo SKOLI_SORT_CLASS ?>"<?php if ($sortby == SKOLI_SORT_CLASS) echo ' class="' . $sortdir_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby', SKOLI_SORT_CLASS)), _("Sort by Class"), 'sortlink', '', '', _("Class")) ?>
+ </th>
+ <th id="s<?php echo SKOLI_SORT_STUDENT ?>"<?php if ($sortby == SKOLI_SORT_STUDENT) echo ' class="' . $sortdir_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby', SKOLI_SORT_STUDENT)), _("Sort by Student Name"), 'sortlink', '', '', _("Student")) ?>
+ </th>
+ <th id="s<?php echo SKOLI_SORT_DATE ?>"<?php if ($sortby == SKOLI_SORT_DATE) echo ' class="' . $sortdir_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby', SKOLI_SORT_DATE)), _("Sort by Date"), 'sortlink', '', '', _("Date")) ?>
+ </th>
+ <th id="s<?php echo SKOLI_SORT_TYPE ?>"<?php if ($sortby == SKOLI_SORT_TYPE) echo ' class="' . $sortdir_class . '"' ?> width="2%">
+ <?php echo Horde::widget(Horde::applicationUrl(Util::addParameter($baseurl, 'sortby', SKOLI_SORT_TYPE)), _("Sort by Type"), 'sortlink', '', '', _("Type")) ?>
+ </th>
+ <th class="nosort">
+ <?php echo _("Entry") ?>
+ </th>
+ </tr>
+</thead>
+<tbody id="entries-body">
--- /dev/null
+<?php
+/**
+ * $Horde: skoli/themes/categoryCSS.php,v 0.1 $
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+
+@define('AUTH_HANDLER', true);
+@define('SKOLI_BASE', dirname(__FILE__) . '/..');
+require_once SKOLI_BASE . '/lib/base.php';
+require_once 'Horde/Image.php';
+require_once 'Horde/Prefs/CategoryManager.php';
+
+header('Content-Type: text/css');
+
+$cManager = new Prefs_CategoryManager();
+
+$colors = $cManager->colors();
+$fgColors = $cManager->fgColors();
+foreach ($colors as $category => $color) {
+ if ($category == '_unfiled_' || $category == '_default_') {
+ continue;
+ }
+
+ $class = '.category' . md5($category);
+
+ echo "$class, .linedRow td$class, .overdue td$class, .closed td$class { "
+ . 'color: ' . (isset($fgColors[$category]) ? $fgColors[$category] : $fgColors['_default_']) . '; '
+ . 'background: ' . $color . '; '
+ . "padding: 0 4px; }\n";
+}
--- /dev/null
+/**
+ * $Horde: skoli/themes/screen.css,v 0.1 $
+ */
+
+/* Menu bottom margin, added for BC. */
+#menu {
+ margin-bottom: 8px;
+}
+
+/* Added for BC. */
+.hidden {
+ display: none;
+}
+.radio {
+ border: 0;
+ height: 14px;
+ width: 14px;
+ background: transparent;
+}
+
+/* Image alignment, added for BC. */
+img {
+ vertical-align: middle;
+}
+
+/* Table CSS, added for BC. */
+th {
+ color: #333;
+ font-size: 90%;
+ border-bottom: 1px solid #999;
+}
+
+/* Sort arrow styles, added for BC. */
+.sortup {
+ background: #bbcbff url("graphics/za.png") center left no-repeat;
+ padding-left: 10px;
+}
+.sortdown {
+ background: #bbcbff url("graphics/az.png") center left no-repeat;
+ padding-left: 10px;
+}
+
+#classes {
+ width: 100%;
+}
+
+.QuickFinderNoMatch {
+ display: none;
+}
+#classes_empty {
+ padding: 4px;
+ font-style: italic;
+}
+
+#quicksearch {
+ display: inline;
+}
+#quicksearch input {
+ background-image: url("graphics/search.png");
+ background-repeat: no-repeat;
+ background-position: 2px center;
+ padding: 1px 0 1px 20px;
+}
+#quicksearch a {
+ display: inline-block;
+ padding: 2px 4px;
+}
+
+/* Added for class list panel */
+body.rightPanel #page {
+ margin-right: 10em;
+}
+body #pageControlsInner {
+ display: none;
+}
+body #sbarShow, body #sbarHide {
+ padding-left: 18px;
+ white-space: nowrap;
+}
+body #sbarShow {
+ display: block;
+ background: transparent url("graphics/plus.png") center left no-repeat;
+}
+body #sbarHide {
+ display: none;
+ background: transparent url("graphics/minus.png") center left no-repeat;
+}
+body.rightPanel #pageControlsInner {
+ display: block;
+}
+body.rightPanel #sbarShow {
+ display: none;
+}
+body.rightPanel #sbarHide {
+ display: block;
+}
+body.rightPanel ul {
+ list-style-type: none;
+}
+
+#pageControls {
+ position: absolute;
+ top: 52px;
+ right: 0;
+ background: #eee;
+ border: 1px solid #ccc;
+ padding: 4px;
+ -moz-border-radius-bottomleft: 10px;
+ -webkit-border-bottom-left-radius: 10px;
+}
+#pageControls #classSearch {
+ background-image: url("graphics/search.png");
+ background-repeat: no-repeat;
+ background-position: 2px center;
+ border: 1px solid #ccc;
+ padding: 2px 0 2px 20px;
+}
+#pageControls h3 {
+ font-size: 100%;
+ margin-top: 1em;
+}
+#pageControls h4 {
+ font-size: 90%;
+ margin-top: .5em;
+}
+
+/* Tables. */
+table#class-list {
+ width: 99%;
+ margin: 0 0 8px 5px;
+ border-top: 1px solid #ddd;
+ border-left: 1px solid #ddd;
+}
+table#class-list th {
+ padding: 3px;
+ background: #e9e9e9;
+ border-right: 1px solid #ccc;
+ text-align: left;
+}
+table#class-list td {
+ padding: 3px;
+ border-right: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+table#class-list th.sortup {
+ background: #bbcbff url("graphics/za.png") center left no-repeat;
+ padding-left: 10px;
+}
+table#class-list th.sortdown {
+ background: #bbcbff url("graphics/az.png") center left no-repeat;
+ padding-left: 10px;
+}
+
+#class-list-buttons {
+ padding: 1em;
+}
+#class-list-buttons form {
+ display: inline;
+}
+.class-list-icon {
+ width: 1%;
+}
+
+.class-info {
+ cursor: pointer;
+}