UI support for creating and maintaining resource groups.
authorMichael J. Rubinsky <mrubinsk@horde.org>
Sat, 26 Sep 2009 21:07:29 +0000 (17:07 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Tue, 29 Sep 2009 20:53:57 +0000 (16:53 -0400)
14 files changed:
kronolith/lib/Driver/Resource.php
kronolith/lib/Forms/CreateResourceGroup.php [new file with mode: 0644]
kronolith/lib/Forms/DeleteResourceGroup.php [new file with mode: 0644]
kronolith/lib/Forms/EditResourceGroup.php [new file with mode: 0644]
kronolith/lib/Resource/Base.php
kronolith/lib/Resource/Group.php
kronolith/lib/Resource/Single.php
kronolith/resources/groups/create.php [new file with mode: 0644]
kronolith/resources/groups/delete.php [new file with mode: 0644]
kronolith/resources/groups/edit.php [new file with mode: 0644]
kronolith/resources/groups/index.php [new file with mode: 0644]
kronolith/resources/index.php
kronolith/templates/resources/form-header.inc [new file with mode: 0644]
kronolith/templates/resources/resource-form.php [new file with mode: 0644]

index 2168911..b17350b 100644 (file)
@@ -368,17 +368,16 @@ class Kronolith_Driver_Resource extends Kronolith_Driver_Sql
                 throw new Horde_Exception($result->getMessage());
             }
         } else {
-            $query = 'INSERT INTO kronolith_resources (resource_id, resource_name, resource_calendar, resource_category, resource_description, resource_response_type)';
-            $cols_values = ' VALUES (?, ?, ?, ?, ?, ?)';
+            $query = 'INSERT INTO kronolith_resources (resource_id, resource_name, resource_calendar, resource_category, resource_description, resource_response_type, resource_type, resource_members)';
+            $cols_values = ' VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
             $id = $this->_db->nextId('kronolith_resources');
-            $values = array($id, $resource->get('name'), $resource->get('calendar'), $resource->get('category'), $resource->get('description'), $resource->get('response_type'));
+            $values = array($id, $resource->get('name'), $resource->get('calendar'), $resource->get('category'), $resource->get('description'), $resource->get('response_type'), $resource->get('type'), $resource->get('members'));
             $result = $this->_write_db->query($query . $cols_values, $values);
-            if (!($result instanceof PEAR_Error)) {
-                return true;
-            } else {
+            if ($result instanceof PEAR_Error) {
                 throw new Horde_Exception($result->getMessage());
             }
-            $resource->setUid($id);
+
+            $resource->setId($id);
         }
 
         return $resource;
diff --git a/kronolith/lib/Forms/CreateResourceGroup.php b/kronolith/lib/Forms/CreateResourceGroup.php
new file mode 100644 (file)
index 0000000..6902ae1
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Horde_Form for creating resource calendars.
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Kronolith
+ */
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Kronolith_CreateResourceForm class provides the form for
+ * creating a calendar.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Kronolith
+ */
+class Kronolith_CreateResourceGroupForm extends Horde_Form {
+
+    function Kronolith_CreateResourceGroupForm(&$vars)
+    {
+        parent::Horde_Form($vars, _("Create Resource"));
+
+        $resources = Kronolith_Resource::listResources(PERMS_READ, array('type' => 'Single'));
+        $enum = array();
+        foreach ($resources as $resource) {
+            $enum[$resource->getId()] = htmlspecialchars($resource->get('name'));
+        }
+        $this->addVariable(_("Name"), 'name', 'text', true);
+        $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60));
+        $this->addVariable(_("Resources"), 'members', 'multienum', false, false, null, array('enum' => $enum));
+        $this->setButtons(array(_("Create")));
+    }
+
+    function execute()
+    {
+        $members = serialize($this->_vars->get('members'));
+        $new = array('name' => $this->_vars->get('name'),
+                     'description' => $this->_vars->get('description'),
+                     'members' => $members);
+
+        $resource = new Kronolith_Resource_Group($new);
+        return $results = Kronolith_Resource::addResource($resource);
+    }
+
+}
diff --git a/kronolith/lib/Forms/DeleteResourceGroup.php b/kronolith/lib/Forms/DeleteResourceGroup.php
new file mode 100644 (file)
index 0000000..d8455c1
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Horde_Form for deleting calendars.
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Kronolith
+ */
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Kronolith_DeleteResourceGroupForm class provides the form for
+ * deleting a calendar.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Kronolith
+ */
+class Kronolith_DeleteResourceGroupForm extends Horde_Form {
+
+    /**
+     * Calendar being deleted
+     */
+    var $_calendar;
+
+    function Kronolith_DeleteResourceGroupForm(&$vars, &$resource)
+    {
+        $this->_resource = &$resource;
+        parent::Horde_Form($vars, sprintf(_("Delete %s"), $resource->get('name')));
+
+        $this->addHidden('', 'c', 'text', true);
+        $this->addVariable(sprintf(_("Really delete the resource \"%s\"? This cannot be undone and all data on this resource will be permanently removed."), $this->_resource->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->_resource->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE))) {
+            return PEAR::raiseError(_("Permission denied"));
+        }
+
+        // Delete the resource.
+        $result = Kronolith::getDriver('Resource')->delete($this->_resource);
+        if ($result instanceof PEAR_Error) {
+            return PEAR::raiseError(sprintf(_("Unable to delete \"%s\": %s"), $this->_resource->get('name'), $result->getMessage()));
+        }
+
+        return true;
+    }
+
+}
diff --git a/kronolith/lib/Forms/EditResourceGroup.php b/kronolith/lib/Forms/EditResourceGroup.php
new file mode 100644 (file)
index 0000000..210fc55
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Horde_Form for editing resource calendars.
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Kronolith
+ */
+
+/** Horde_Form */
+require_once 'Horde/Form.php';
+
+/** Horde_Form_Renderer */
+require_once 'Horde/Form/Renderer.php';
+
+/**
+ * The Kronolith_EditResourceForm class provides the form for
+ * editing a calendar.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Kronolith
+ */
+class Kronolith_EditResourceGroupForm extends Horde_Form {
+
+    /**
+     * Calendar being edited
+     */
+    var $_resource;
+
+    function Kronolith_EditResourceGroupForm(&$vars, &$resource)
+    {
+        $this->_resource = &$resource;
+        parent::Horde_Form($vars, sprintf(_("Edit %s"), $resource->get('name')));
+
+        $resources = Kronolith_Resource::listResources(PERMS_READ, array('type' => 'Single'));
+        $enum = array();
+        foreach ($resources as $r) {
+            $enum[$r->getId()] = htmlspecialchars($r->get('name'));
+        }
+
+        $this->addHidden('', 'c', 'text', true);
+        $this->addVariable(_("Name"), 'name', 'text', true);
+        $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60));
+        $this->addVariable(_("Resources"), 'members', 'multienum', false, false, null, array('enum' => $enum));
+
+        $this->setButtons(array(_("Save")));
+    }
+
+    function execute()
+    {
+        $original_name = $this->_resource->get('name');
+        $new_name = $this->_vars->get('name');
+        $this->_resource->set('name', $new_name);
+        $this->_resource->set('description', $this->_vars->get('description'));
+        $this->_resource->set('members', serialize($this->_vars->get('members')));
+        if ($original_name != $new_name) {
+            $result = Kronolith::getDriver()->rename($original_name, $new_name);
+            if (is_a($result, 'PEAR_Error')) {
+                return PEAR::raiseError(sprintf(_("Unable to rename \"%s\": %s"), $original_name, $result->getMessage()));
+            }
+        }
+
+        $result = $this->_resource->save();
+        if (is_a($result, 'PEAR_Error')) {
+            return PEAR::raiseError(sprintf(_("Unable to save resource \"%s\": %s"), $new_name, $result->getMessage()));
+        }
+
+        return $this->_resource;
+
+    }
+
+}
index 20ad896..c59091f 100644 (file)
@@ -12,12 +12,10 @@ abstract class Kronolith_Resource_Base
      *
      *   name        - Display name of resource.
      *   calendar    - The calendar associated with this resource.
-     *   category    - The category of this resource...an arbitrary label used
-     *                 to group multiple resources for the resource_group implementation
+     *
      *   description -
      *   email       -
      *   response_type - a RESPONSETYPE_* constant
-     *   max_reservations
      *
      * @var array
      */
@@ -99,7 +97,7 @@ abstract class Kronolith_Resource_Base
     {
         $property = str_replace('resource_', '', $property);
         if ($property == 'type' && empty($this->_params['type'])) {
-            return (self instanceof Kronolith_Resource_Single) ? 'Single' : 'Group';
+            return ($this instanceof Kronolith_Resource_Single) ? 'Single' : 'Group';
         }
 
         return !empty($this->_params[$property]) ? $this->_params[$property] : false;
index 457814e..575a0cb 100644 (file)
@@ -38,6 +38,8 @@ class Kronolith_Resource_Group extends Kronolith_Resource_Base
      */
     public function __construct($params)
     {
+        $params['resource_type'] = 'Group';
+
         parent::__construct($params);
         $this->_driver = $this->getDriver();
     }
@@ -178,7 +180,11 @@ class Kronolith_Resource_Group extends Kronolith_Resource_Base
 
     public function setId($id)
     {
-        throw new Horde_Exception('Unsupported');
+        if (empty($this->_id)) {
+            $this->_id = $id;
+        } else {
+            throw new Horde_Exception(_("Resource already exists. Cannot change the id."));
+        }
     }
 
     /**
index 82f4bdf..b40e175 100644 (file)
@@ -134,7 +134,7 @@ class Kronolith_Resource_Single extends Kronolith_Resource_Base
 
     public function setId($id)
     {
-        if (!empty($this->_id)) {
+        if (empty($this->_id)) {
             $this->_id = $id;
         } else {
             throw new Horde_Exception(_("Resource already exists. Cannot change the id."));
diff --git a/kronolith/resources/groups/create.php b/kronolith/resources/groups/create.php
new file mode 100644 (file)
index 0000000..03b3a33
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright 2009 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>
+ */
+
+require_once dirname(__FILE__) . '/../../lib/base.php';
+require_once KRONOLITH_BASE . '/lib/Forms/CreateResourceGroup.php';
+
+// Exit if this isn't an authenticated, administrative user
+if (!Horde_Auth::isAdmin()) {
+    header('Location: ' . Horde::applicationUrl($prefs->getValue('defaultview') . '.php', true));
+    exit;
+}
+
+$vars = Horde_Variables::getDefaultVariables();
+$form = new Kronolith_CreateResourceGroupForm($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 calendar \"%s\" has been created."), $vars->get('name')), 'horde.success');
+    }
+
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+}
+
+$title = $form->getTitle();
+require KRONOLITH_TEMPLATES . '/common-header.inc';
+require KRONOLITH_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'create.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/kronolith/resources/groups/delete.php b/kronolith/resources/groups/delete.php
new file mode 100644 (file)
index 0000000..f0e2b58
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Copyright 2002-2009 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>
+ */
+
+require_once dirname(__FILE__) . '/../../lib/base.php';
+require_once KRONOLITH_BASE . '/lib/Forms/DeleteResourceGroup.php';
+
+// Exit if this isn't an authenticated administrative user.
+if (!Horde_Auth::isAdmin()) {
+    header('Location: ' . Horde::applicationUrl($prefs->getValue('defaultview') . '.php', true));
+    exit;
+}
+
+$vars = Horde_Variables::getDefaultVariables();
+$d = Kronolith::getDriver('Resource');
+$resource = $d->getResource($vars->get('c'));
+
+if ($resource instanceof PEAR_Error) {
+    $notification->push($resoruce, 'horde.error');
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+} elseif (!$resource->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) {
+    $notification->push(_("You are not allowed to delete this resource group."), 'horde.error');
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+}
+
+$form = new Kronolith_DeleteResourceGroupForm($vars, $resource);
+
+// Execute if the form is valid (must pass with POST variables only).
+if ($form->validate(new Horde_Variables($_POST))) {
+    $result = $form->execute();
+    if ($result instanceof PEAR_Error) {
+        $notification->push($result, 'horde.error');
+    } elseif ($result) {
+        $notification->push(sprintf(_("The resource group \"%s\" has been deleted."), $resource->get('name')), 'horde.success');
+    }
+
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+}
+
+$title = $form->getTitle();
+require KRONOLITH_TEMPLATES . '/common-header.inc';
+require KRONOLITH_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'delete.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/kronolith/resources/groups/edit.php b/kronolith/resources/groups/edit.php
new file mode 100644 (file)
index 0000000..f53bfe5
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Copyright 2002-2009 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>
+ */
+
+require_once dirname(__FILE__) . '/../../lib/base.php';
+require_once KRONOLITH_BASE . '/lib/Forms/EditResourceGroup.php';
+
+// Exit if this isn't an authenticated administrative user.
+if (!Horde_Auth::isAdmin()) {
+    header('Location: ' . Horde::applicationUrl($prefs->getValue('defaultview') . '.php', true));
+    exit;
+}
+
+$vars = Horde_Variables::getDefaultVariables();
+$d = Kronolith::getDriver('Resource');
+$group = $d->getResource($vars->get('c'));
+
+if ($group instanceof PEAR_Error) {
+    $notification->push($group, 'horde.error');
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+} elseif (!$group->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
+    $notification->push(_("You are not allowed to change this resource."), 'horde.error');
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+}
+$form = new Kronolith_EditResourceGroupForm($vars, $group);
+
+// Execute if the form is valid.
+if ($form->validate($vars)) {
+    $original_name = $group->get('name');
+    $result = $form->execute();
+    if ($result instanceof PEAR_Error) {
+        $notification->push($result, 'horde.error');
+    } else {
+        if ($result->get('name') != $original_name) {
+            $notification->push(sprintf(_("The resource group \"%s\" has been renamed to \"%s\"."), $original_name, $group->get('name')), 'horde.success');
+        } else {
+            $notification->push(sprintf(_("The resource group \"%s\" has been saved."), $original_name), 'horde.success');
+        }
+    }
+
+    header('Location: ' . Horde::applicationUrl('resources/groups/', true));
+    exit;
+}
+
+$vars->set('name', $group->get('name'));
+$vars->set('description', $group->get('description'));
+$vars->set('members', unserialize($group->get('members')));
+
+$title = $form->getTitle();
+require KRONOLITH_TEMPLATES . '/common-header.inc';
+require KRONOLITH_TEMPLATES . '/menu.inc';
+echo $form->renderActive($form->getRenderer(), $vars, 'edit.php', 'post');
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/kronolith/resources/groups/index.php b/kronolith/resources/groups/index.php
new file mode 100644 (file)
index 0000000..046cf6d
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+require_once dirname(__FILE__) . '/../../lib/base.php';
+
+$title = _("Resource Groups");
+
+require KRONOLITH_TEMPLATES . '/common-header.inc';
+require KRONOLITH_TEMPLATES . '/menu.inc';
+
+// Exit if this isn't an authenticated user.
+if (!Horde_Auth::getAuth()) {
+    header('Location: ' . Horde::applicationUrl($prefs->getValue('defaultview') . '.php'));
+    exit;
+}
+$edit_url_base = Horde::applicationUrl('resources/groups/edit.php');
+$edit_img = Horde::img('edit.png', _("Edit"), null, $registry->getImageDir('horde'));
+
+$resources = Kronolith_Resource::listResources(PERMS_EDIT, array('type' => 'Group'));
+//$display_url_base = Horde::applicationUrl('month.php', true, -1);
+$delete_url_base = Horde::applicationUrl('resources/groups/delete.php');
+$delete_img = Horde::img('delete.png', _("Delete"), null, $registry->getImageDir('horde'));
+?>
+<script type="text/javascript">
+function performAction(action, rid)
+{
+    document.resourceform.actionId.value = action;
+    document.resourceform.actionValue.value = rid;
+    document.resourceform.submit();
+    return false;
+}
+</script>
+<!-- Break out into template -->
+<div id="page">
+
+<h1 class="header">
+ <?php echo _("Resources") ?>
+</h1>
+<?php if ($isAdmin = Horde_Auth::isAdmin()): ?>
+ <form method="get" action="create.php">
+  <?php echo Horde_Util::formInput() ?>
+  <input type="submit" class="button" value="<?php echo _("Create a new Resource Group") ?>" />
+  <a class="button" href="<?php echo Horde::applicationUrl('resources')?>"><?php echo _("Return to Single Resources")?></a>
+ </form>
+<?php endif ?>
+<table summary="<?php echo _("Resource Group List") ?>" cellspacing="0" id="calendar-list" class="striped sortable">
+ <thead>
+  <tr>
+   <th>&nbsp;</th>
+   <th class="sortdown"><?php echo _("Name") ?></th>
+   <th><?php echo _("Description") ?></th>
+  </tr>
+ </thead>
+ <tbody>
+<?php foreach ($resources as $resource): ?>
+ <tr>
+  <?php if ($isAdmin):?>
+  <td>
+       <?php echo Horde::link(Horde_Util::addParameter($delete_url_base, 'c', $resource->getId()), _("Delete")) . $delete_img . '</a>' ?>
+       <?php echo Horde::link(Horde_Util::addParameter($edit_url_base, 'c', $resource->getId()), _("Edit")) . $edit_img . '</a>' ?>
+  <?php else:?>
+  <td>&nbsp;</td>
+  <?php endif;?>
+  <td><?php echo htmlspecialchars($resource->get('name')) ?></td>
+  <td><?php echo htmlspecialchars($resource->get('description')) ?></td>
+ </tr>
+<?php endforeach; ?>
+</tbody>
+</table>
+
+<?php
+/**
+ * Show just the beginning and end of long URLs.
+ */
+function shorten_url($url, $separator = '...', $first_chunk_length = 35, $last_chunk_length = 15)
+{
+    $url_length = strlen($url);
+    $max_length = $first_chunk_length + strlen($separator) + $last_chunk_length;
+
+    if ($url_length > $max_length) {
+        return substr_replace($url, $separator, $first_chunk_length, -$last_chunk_length);
+    }
+
+    return $url;
+}
+?>
+</div>
\ No newline at end of file
index dcd3dfb..0d21a40 100644 (file)
@@ -17,7 +17,7 @@ if (!Horde_Auth::getAuth()) {
 }
 $edit_url_base = Horde::applicationUrl('resources/edit.php');
 $edit_img = Horde::img('edit.png', _("Edit"), null, $registry->getImageDir('horde'));
-$resources = Kronolith_Resource::listResources();
+$resources = Kronolith_Resource::listResources(PERMS_READ, array('type' => 'Single'));
 $display_url_base = Horde::applicationUrl('month.php', true, -1);
 $delete_url_base = Horde::applicationUrl('resources/delete.php');
 $delete_img = Horde::img('delete.png', _("Delete"), null, $registry->getImageDir('horde'));
@@ -41,6 +41,7 @@ function performAction(action, rid)
  <form method="get" action="create.php">
   <?php echo Horde_Util::formInput() ?>
   <input type="submit" class="button" value="<?php echo _("Create a new Resource") ?>" />
+  <a class="button" href="<?php echo Horde::applicationUrl('resources/groups') ?>"><?php echo _("Manage Resource Groups")?> </a>
  </form>
 <?php endif ?>
 <table summary="<?php echo _("Resource List") ?>" cellspacing="0" id="calendar-list" class="striped sortable">
diff --git a/kronolith/templates/resources/form-header.inc b/kronolith/templates/resources/form-header.inc
new file mode 100644 (file)
index 0000000..3c25e34
--- /dev/null
@@ -0,0 +1,14 @@
+<script type="text/javascript">
+
+function setAction(action, cid)
+{
+    $('actionID').value = action;
+    $('cid').value = cid;
+    document.resourcesForm.submit();
+    return false;
+}
+
+</script>
+<form name="resourcesForm" method="post" action="<?php echo Horde::selfUrl(false)?>">
+<input type="hidden" id="actionID" name="actionID" value="view" />
+<input type="hidden" id="cid" name="cid" />
diff --git a/kronolith/templates/resources/resource-form.php b/kronolith/templates/resources/resource-form.php
new file mode 100644 (file)
index 0000000..b3d9bbc
--- /dev/null
@@ -0,0 +1 @@
+<?php